一、单一职责原则
单一职责原则的英文:Single Responsibility Principle,简称是SRP
单一职责原则的定义是:应该有且仅有一个原因引起类的变更
也就是一个接口或类只有一个职责,它只负责一件事情。
类图中的关系表示:
书上举例的一个图:
这个接口设计是不正确的,应该把用户的属性和用户的行为分开,如下图所示:
重新拆分成两个接口,分别是:
IUserBO(business object,业务对象),负责用户属性的收集和反馈;
IUserBiz(business logic,业务逻辑),负责用户的行为,完成用户信息的维护和变更。
还有一个举例:
把打电话的三个功能:
1、创建链接(拨号+接电话什么的)
2、数据传输(打电话、说话)
3、关闭连接(挂电话)
拆分成两个接口,分别对应:连接管理和数据传输
再用一个phone类来实现这两个接口
下面是我的想法:
这样拆分一方面使得接口的职责非常清晰,还可以实现接口功能的重用?比如数据传输功能
单一职责适用于接口、类,同时也适用于方法。一个方法尽可能做一件事情,方法职责要清晰。
但是类的单一职责收到很多因素制约,现实生活中很难实现(不太理解)。
单一设计原则的好处:
1、类的复杂性降低,一个类实现什么职责有清晰明确的定义;
2、可读性提高,复杂性降低;
3、可维护性提高;
二、里氏替换原则
LSP
面向对象中的继承,有下面优点:
1、代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
2、提高代码的重用性;
3、子类与父类相似,但是又可以有自己特别的功能
4、提高代码的可扩展性;
有下面缺点:
1、继承是侵入性的,只要继承,就必须拥有父类的所有属性和方法(有限制的吧?比如private);
2、降低代码的灵活性;
3、增强了耦合性。当父类的常量、变量和方法发生修改时,需要考虑子类的修改,甚至需要考虑代码的重构。
为了更好的发挥继承的优点,引入里氏替换原则
通俗地讲:在一个程序中父类出现的地方,都可以替换成他的子类,也不会产生任何错误或异常。
反过来不行,子类出现的地方,不能替换成父类。
一句话包含了4层含义:
1、子类必须完全实现父类的方法
比如:枪的主要职责是射击,在抽象类gun中有一个未定义方法shoot(),该方法在gun的子类中有具体的实现。
还有一个玩具枪的例子,不是特别明白。
2、子类可以有自己的个性
3、覆盖或实现父类的方法时,输入参数可以被放大。(重载?)
4、覆盖或实现父类的方法时,返回类型可以被缩小。(重写?)(没有实践出什么结果)
关于2、3、4
可以理解为:子类可以扩展父类的功能,但不能改变父类原有的功能。
比如这个例子:
这个例子其实有点粗糙,没有体现出如果子类的参数类型比父类大,可以避免这一情况
class A{public int func1(int a, int b){return a-b;}}public class Client{public static void main(String[] args){A a = new A();System.out.println("100-50="+a.func1(100, 50));System.out.println("100-80="+a.func1(100, 80));}}
class B extends A{public int func1(int a, int b){return a+b;}public int func2(int a, int b){return func1(a,b)+100;}}public class Client{public static void main(String[] args){B b = new B();System.out.println("100-50="+b.func1(100, 50));System.out.println("100-80="+b.func1(100, 80));}}
因为B重写了A的方法func1(),导致把原来Client中的a替换成B类型之后,运行结果发生了改变。
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
三、依赖倒置原则
依赖导致原则(Dependency Inversion Principle, DIP)
1、模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
2、接口或抽象类不依赖于实现类;
3、实现类依赖接口或抽象类;
依赖倒置原则的优点:
1、减少类之间的耦合性
2、提高系统的稳定性,降低并行开发引起的风险
3、提高代码的可读性和可维护性。
依赖倒置的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。
比如被依赖者的变更,不应该让依赖者来承担修改成本。
一个模块的变更不应该波及其他的模块。
在项目中应该注意:
1、每个类都尽量有接口或抽象类
2、变量的表面类型尽量是接口或者是抽象类
3、任何类都不应该从具体类派生
4、尽量不要覆盖基类的方法
5、结合里氏替换原则使用
要结合实际情况,不是一定要严格遵循这个原则。
依赖的3种写法
1、构造函数传递依赖对象
public class Driver implements IDriver{private ICar car;public Driver(ICar _car){car=_car;}@Overridepublic void drive() {this.car.run();}}
2、Setter依赖注入
public class Driver implements IDriver{private ICar car;public void setCar(ICar _car){this.car=car;}@Overridepublic void drive() {this.car.run();}}
3、接口声明依赖对象
public class Driver implements IDriver{
@Override
public void drive(ICar car) {
car.run();
}
}
四、接口隔离原则
接口分两种:
1、实例接口(Object Interface),就是平时用的类,可以用new关键字产生一个实例,类也是接口。
2、类接口(Class Interface),使用Interface关键字定义的接口
接口要尽量细化,同时接口中的方法尽量少。(和单一职责不一样的,感觉角度不同)
书里面举例了一个脸蛋好看、身材火辣、知性优雅美女的例子。
如何保证接口的纯洁性
1、接口要尽量小
但是不能破坏单一职责原则
2、接口要高内聚
在接口中尽量少的公布public方法,接口是对外的承诺,承诺越小变更的风险也就越小
3、定制服务
体现在方法上,比如下面这个例子:
4、接口的设计是有限度的
五、迪米特法则
也称为最少知识原则,即:一个类应该对自己需要调用的类知道的最少,其他类的内部如何复杂都与我无关,我只需要了解他们提供的public方法就好。
迪米特法则的核心观念就是类间解耦、弱耦合,提高类的复用率,提高系统的健壮性。
但是可能导致产生大量的中转或跳转类,导致系统的复杂性提高。要注意权衡。
迪米特法则对低耦合提出了明确的要求,包含下面4层含义:
1、只和朋友交流(有依赖关系)
书中举了一个老师,组长和学生的例子
出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的不属于朋友类。
2、朋友间也是有距离的
尽量不要对外公布太多的Public方法和非静态的public方法。
3、是自己的就是自己的
如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
4、谨慎使用Serializable
同步更新的问题?
六、开闭原则
开闭原则对扩展开放,对修改关闭。
但是,并不意味着不做任何修改,低层次模块代码的变更,必然要有高层次模块进行耦合,否则就是一个孤立无意义的代码片段。
有一个书店的例子,下面是书店售书类图,书店可以获取每本书的名字、价格、作者。
假设现在需要完成促销活动的功能,有的书打八折,有的书打九折。
在需求发生变化时,如何修改?
1、修改接口
在IBook接口上新增getOffPrice(),专门进行打折处理,但这样所有的实现类要修改,BookStore中的main方法也需要修改,IBook作为一个接口应该是稳定可靠的(依赖倒置原则,被依赖者的变更,不应该让依赖者来承担修改成本),所以此方法不可行。
2、修改实现类
可以,但是这样会导致IBook接口的getPrice()接口只能获取到打折后的价格。
3、通过扩展类实现变化
增加一个子类OffNovelBook,覆写getPrice方法,高层次的模块(main方法)通过OffNovelBook类产生新的对象,完成业务变化对系统的最小化开发。这是相比之下最好的办法,修改少,风险也小。
为什么要采用开闭原则?
开闭原则是最基础的一个原则,前面五个小节介绍的原则都是开闭原则的具体形态。
可以理解成开闭原则是抽象类,其他五大原则是具体的实现类。
1、开闭原则对测试的影响
在有变化提出时,原有的健壮代码是否可以不修改,仅仅通过扩展实现变化?否则,需要把原有的测试代码回炉重造,单元测试需要重新写过。
2、开闭原则可以提高复用性
代码的粒度越小,被复用的可能性就越大。
3、开闭原则可以提高可维护性
4、面向对象开发的要求
如何使用开闭原则
开闭原则是一个非常虚的原则,前面5个原则是对开闭原则的具体解释。
1、抽象约束
第一、通过接口或抽象类约束扩展,不允许实现类中出现接口或抽象类中不存在的Public方法;(迪米特法则)
第二、参数类型,引用对象尽量使用接口或抽象类,而不是具体实现类。(依赖倒置原则)
第三、抽象层尽量保证稳定,一旦确定不允许修改。(依赖倒置原则)
2、元数据控制模块行为
控制反转,通过扩展一个子类/修改配置文件,来完成业务的变化
3、制定项目章程
4、封装变化
第一、将相同的变化封装到一个接口或抽象类中;(单一职责原则)
第二、将不同的变化封装到不同的接口或抽象类中;
(不是很明白)
