在大话设计模式六原则专场一中我们让单一职责原则、开闭原则和依赖倒置原则自报家门,接下来,另外三个原则也要闪亮登场!
含义:子类型必须能够替换掉它们的父类型,有了里氏代换原则,使得开闭原则成了可能,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
使用说明:
(1)子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。
(2)我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
(3)Java语言中,在编译阶段,Java编译器会检查一个程序是否符合里氏代换原则,这是一个与实现无关的、纯语法意义上的检查,但Java编译器的检查是有局限的。【所有引用基类(父类)的地方必须能透明地使用其子类的对象】
通俗理解:狗是动物的子类,我喜欢动物,说明我一定喜欢狗——我喜欢动物(父类)的这份情感渗透到了狗狗(子类)身上。
看图说话:
客户(Customer)可以分为VIP客户(VIPCustomer)和普通客户(CommonCustomer)两类,系统需要提供一个发送Email的功能,原始设计方案如下图所示:
在对系统进行进一步分析后发现,无论是普通客户还是VIP客户,发送邮件的过程都是相同的,也就是说两个send()方法中的代码重复,而且在本系统中还将增加新类型的客户。为了让系统具有更好的扩展性,同时减少代码重复,使用里氏代换原则对其进行重构——增加一个新的抽象客户类Customer,而将CommonCustomer和VIPCustomer类作为其子类,邮件发送类EmailSender类针对抽象客户类Customer编程,根据里氏代换原则,能够接受基类对象的地方必然能够接受子类对象,因此将EmailSender中的send()方法的参数类型改为Customer,如果需要增加新类型的客户,只需将其作为Customer类的子类即可,如下图:
我的理解:一人吃饱,全家不饿!一人得道,鸡犬升天!
前提:在类的结构设计上,每一个类都应当尽量降低成员的访问权限。
看图说话:下图中,由于界面控件之间的交互关系复杂,导致在该窗口中增加新的界面控件时需要修改与之交互的其他控件的源代码,系统扩展性较差,也不便于增加和删除新控件。
用迪米特对其进行重构,通过引入一个专门用于控制界面控件交互的中间类(Mediator)来降低界面控件之间的耦合度。引入中间类之后,界面控件之间不再发生直接引用,而是将请求先转发给中间类,再由中间类来完成对其他控件的调用。当需要增加或删除新的控件时,只需修改中间类即可,无须修改新增控件或已有控件的源代码,重构后如下图所示:
我的理解:高山流水,朋友只认定一人。
含义:尽量使用合成/聚合,尽量不要使用类继承。
原因:继承也是一种依赖关系,它限制了灵活性,并最终限制了复用性。
看图说话:
如果银行卡默认都拥有了存款、取款和透支的功能,那么我们办理的卡都将具有这个功能,此时使用了继承关系:
为了灵活地拥有各种功能,此时可以分别设立储蓄卡和信用卡两种,并有银行卡来对它们进行聚合使用,此时采用了合成复用原则:
扩充知识:组合和聚合都是关联关系,前者比后者表示的整体与部分间关系更强烈,原因是组合关系的类具有相同的生命周期(不能存在独立性),就像下图所示:
我的理解:顾名思义,先合成,再复用,真的很像单一职责模式中的“先总后分”。
总结:结合前一篇文章,总结完这几个原则以后发现,分成了6个,实际上只是侧重点不一样罢了,他们都是公用一个出发点——高内聚低耦合,6个原则为我们提前声明了这个最高级出发点后,又作为全书的主线去指导那23个模式,很像我们自考时候接触的职能制组织结构。