面向对象设计原则
1、开闭原则
-
开闭原则理解:
简单说就是一个软件实体支持扩展,不支持修改。就是在不改变源码的基础上,扩展其它的功能。
其实笔者认为,开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
-
开闭原则实现:
开闭原则主要实现于接口或抽象类,接口和抽象类只提供功能(即不可修改),功能的具体扩展需要定义实现类去实现(即可扩展)。
-
开闭功能的好处:
通过扩展已有软件系统,可提供新的行为,以满足对软件的新需求,提高了软件系统的适应性和灵活性,特别是最重要的抽象层模块不能再修改,提高了软件系统的一定的稳定性和延续性,这样的设计同时也满足了可复用性与可维护性;
2、单一职责
-
系统中的每一个类都应该只有一个职责,而所有类所关注的就是自身职责的完成。
一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小。而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
-
为什么要遵守单一职责原则?
1、提高类的可维护性和可读写性
一个类的职责少了,复杂度降低了,代码就少了,可读性也就好了,可维护性自然就高了。
2、提高系统的可维护性
系统是由类组成的,每个类的可维护性高,相对来讲整个系统的可维护性就高。当然,前提是系统的架构没有问题。
3、降低变更的风险
一个类的职责越多,变更的可能性就更大,变更带来的风险也就越大。
-
如何遵守单一职责原则
合理的职责分解,相同的职责放到一起,不同的职责分解到不同的接口和实现中去
3、里氏替换原则
里氏代换原则的定义是——子类型必须能够替换掉他们的父类型。
如果一个软件实体使用的是一个父类的话,那么就肯定适用其子类,并且该软件实体察觉不到父类对象和子类对象的区别。
举个例子,有个很厉害的老木匠,周边的邻居都找他做家具,老木匠还有个大徒弟,不但学全了老木匠的本事还青出于蓝而胜于蓝。后来老木匠干不动了,所以每次有人找上门来都是老木匠接活儿,大徒弟去做家具,而对做家具的人来说,他们并不关心具体谁做的,只要能拿到家具就好。
只有在满足了里氏替换原则之后,也就是子类可以代替父类出现,并且不影响程序的功能时,父类才能真正得到复用,并且子类可以在父类的基础上添加新的行为,面向对象思想中,父类和子类的继承关系是抽象化的具体实现。
4、依赖倒转原则
定义如下:
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
另一种表述为:要针对接口编程,不要针对实现编程。
1 public class Driver { 2 //司机的主要职责就是驾驶汽车 3 public void drive(ICar car){ 4 car.run(); 5 } 6 } 7 //将汽车模块抽象为一个接口:可以是奔驰汽车,也可以是宝马汽车 8 public interface ICar { 9 //是汽车就应该能跑 10 public void run(); 11 } 12 public class Benz implements ICar{ 13 //汽车肯定会跑 14 public void run(){ 15 System.out.println("奔驰汽车开始运行..."); 16 } 17 } 18 public class BMW implements ICar{ 19 //宝马车当然也可以开动了 20 public void run(){ 21 System.out.println("宝马汽车开始运行..."); 22 } 23 } 24 //高层模块 25 public class Client { 26 public static void main(String[] args) { 27 IDriver xiaoLi = new Driver(); 28 ICar benz = new Benz(); 29 //小李开奔驰车 30 xiaoLi.drive(benz); 31 } 32 } 33
面向接口编程,司机依赖Icar接口,而不依赖实现类
5、接口隔离原则
1、客户端不应依赖它不需要的接口
2、类间的依赖关系应该建立在最小的接口上
其实通俗来理解就是,不要在一个接口里面放很多的方法,这样会显得这个类很臃肿。接口应该尽量细化,一个接口对应一个功能模块,同时接口里面的方法应该尽可能的少,使接口更加灵活轻便。
或许有的人认为接口隔离原则和单一职责原则很像,但两个原则还是存在着明显的区别。单一职责原则是在业务逻辑上的划分,注重的是职责。接口隔离原则是基于接口设计考虑。
例如一个接口的职责包含10个方法,这10个方法都放在同一接口中,并且提供给多个模块调用,但不同模块需要依赖的方法是不一样的,这时模块为了实现自己的功能就不得不实现一些对其没有意义的方法,这样的设计是不符合接口隔离原则的。接口隔离原则要求"尽量使用多个专门的接口"专门提供给不同的模块。
6、合成复用原则
合成复用原则定义
合成复用原则(Composite Reuse Principle, CRP)又称为组合/聚合复用原则(Composition/ Aggregate Reuse Principle, CARP),其定义如下:
尽量使用对象组合,而不是继承来达到复用的目的。
包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
在面向对象设计中,可以通过两种基本方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承。
继承复用:实现简单,易于扩展。破坏系统的封装性,从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用 )
组合/聚合复用:耦合度相对较低,选择性地调用成员对象的操作;可以在运行时动态进行。(“黑箱”复用 )
7、迪米特法则
也叫最少知识原则。迪米特法则的定义是只与你的直接朋友交谈,不与"陌生人"说话。如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该应用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的朋友是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
优点:
1、降低类之间的耦合度,提高模块的相对独立性。
2、由于亲和度降低,从而提高了类的可复用率和系统的扩展性。
缺点:
过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
使用迪米特法则需要注意:
1、在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
2、在类的结构设计上,尽量降低类成员的访问权限。
3、在类的设计上,优先考虑将一个类设置成不变类。
4、在对其他类的引用上,将引用其他对象的次数降到最低。
5、不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
6、谨慎使用序列化(Serializable)功能。
经典案例:
明星与经纪人的关系实例。明星负责演出,经纪人负责处理日常事务,如与粉丝的见面会,与媒体公司的业务洽淡等。