[DesignPattern] 设计之禅读书笔记(一)
-
六大原则
-
单一职责原则:
- There should be no more than one reason for a class to change
- 最佳实践: 接口的设计一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
-
里氏替换原则:
-
Functions that use pointers or reference to base classes must be able to use objects of derived classes without knowing it (所有引用基类的地方必须能透明的使用其子类的对象)
-
包含意义:
-
子类必须完全实现父类的方法。
- 如果子类不能完全实现父类的方法, 或者父类的某些方法在子类中已经发生“畸变”, 则建议断开父子继承关系,采用依赖,聚合,组合等关系代替继承。
-
子类可以有自己的个性。
- 有子类出现的地方未必可以用父类代替
-
子类覆盖或实现父类的方法时输入参数可以被放大
- 子类的输入参数的类的范围要大于父类(子类的前置条件要大于父类的前置条件),如果父类是 public void func(HashMap hashmap), 则子类的覆盖需要是 public void func(Map map)
-
子类覆写或实现父类的方法时输出结果可以被缩小
- 父类的一个方法的返回值时一个类型T, 子类的相同方法(重载或覆写)的返回值时S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。
-
-
最佳实践: 采用里氏替换原则时, 尽量避免子类的“畸变个性”,一旦子类有“个性”, 这个子类和父类之间的关系就很难调和了,把子类当作父类使用,子类的“个性”被抹杀————委屈了点:把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离————缺乏类替换的标准。
-
-
依赖倒置原则
-
High level modules should not depend up low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. etails should depend upon abstractions.
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
-
在Java中,只要定义变量就必然要有类型, 一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型。
- IDriver zhangSan = new Driver(); zhangSan的表面类型是IDriver, 实际类型是Driver。
-
依赖的三种写法
-
构造函数传递依赖对象
public interface IDriver{ public void drive(); } public class Driver implements IDriver{ private ICar car; public Driver(Icar car) { this.car = car; } @Override public void drive(){ this.car.run(); } }
-
Setter方法传递依赖
-
接口声明依赖对象
public interface IDriver{ public void setCar(ICar car); public void drive(); } public class Driver implements IDriver { private Icar car; public void setCar(Icar car) { this.car = car; } public void drive() { this.car.run(); } }
-
-
最佳实践
-
依赖倒置原则的本质就是通过抽象类或者接口,使各个类或者模块的实现彼此独立,互不影响,实现模块间的松耦合。其核心就是“面向接口编程”
-
每个类尽量都有接口或者抽象类,或者两者皆具备。
-
变量的表面类尽量都是接口或者抽象类
- 但一般xxxUtils不需要接口或者抽象类
- 如果使用类的clone方法,就必须使用现实类,这个是JDK提供的一个规范
-
任何类都不应该从具体类派生
- 万一有也不能超过两层依赖
-
尽量不要覆写(override)基类的方法
- 如果基类是一个抽象类,而且这个方法已经实现了,子类尽量不要覆写,不然破坏依赖的稳定性。
-
结合里氏替换原则使用
- 接口负责定义public属性和方法, 并且声明与其他对象的依赖关系
- 抽象类负责公共构造部分的实现,
- 实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。
-
-
-
-
接口隔离原则
-
Clients should not be forced to depend upon interfaces that they don't use. (客户端不应该依赖它不需要的接口)
-
The dependency of one class to another one should depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上)
-
总结: 建立单一接口, 不要建立臃肿庞大的接口。接口尽量要细化,同时接口中的方法尽量少。
-
保持接口的纯洁性
- 接口尽量要小,但根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
- 接口要高内聚: 高内聚就是提高接口、类、模块的处理能力,减少对外的交互。在接口中尽量减少公布public方法, 接口时对外的承诺, 承诺越少对系统的开发越有利,变更的风险越少, 同时有利于降低成本。
- 定制原则
- 接口设计时有限度的
-
最佳实践:
- 一个接口只服务一个子模块或业务逻辑;
- 通过业务逻辑压缩接口中的public方法,接口时常回顾,尽量让接口达到“满身筋骨肉”, 而不是“肥嘟嘟”的一大堆方法;
- 已经被污染了的接口, 尽量区修改,若变更的风险较大, 则采用适配器模式进行转化处理;
- 了解环境,拒绝盲从。
-
-
迪米特法则(Law of Demeter, LoD)
-
一个对象应该对其他对象有最少的了解。
-
只和朋友交流:
- Only talk to your immediate friends.
- 朋友类的定义:出现在成员变量、方法的输入输出参数中的类称为成员的朋友类, 而出现在方法内部的类不属于朋友类。(例如,在方法中new一个类,这个不属于朋友, 应该避免)
- 一个类只和朋友类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问, 即每一个点号后面的返回类都相同),类于类之间的关系时建立在类间的,而不是方法间, 因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。
-
朋友间也是有距离的:
- 在设计需要反复衡量:是否可以再减少public方法和属性, 是否可以修改private,package-private(包类型, 再类、方法、变量前不加访问权限,则默认为包类型), protected等访问权限, 是否可以加上final关键字等。
- 迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量, 尽量内敛, 多使用private, package-private, protected等访问权限。
-
是自己的就是自己的
- 如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置再本类中。
-
谨慎使用Serializable
-
-
最佳实践:
- 核心:类间解耦,弱耦合
- 再实际应用中,如果一个类跳转了两次以上才能访问到另一个类,就需要想办法进行重构了, 跳转次数越多,系统越复杂, 维护就越困难。
-
-
开闭原则
-
Software entities like classes, modules and functions should be open for extension but closed for modifications. (一个软件实体如类, 模块和函数应该对扩展开放,对修改关闭。)
-
开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合, 否则就是一个孤立无意义的代码片段。
-
变化的种类:
- 逻辑变化: 变化是一个逻辑,不设计其他模块,eg. ab+c => ab*c 这种变化通过修改原有类中的方法来完成,前提是所有依赖或关联类都按照相同的逻辑处理
- 子模块变化: 低层次的模块变化必然引起高层模块的变化,因此在通过扩展变化时, 高层次的模块修改时必然的。
- 可见视图变化
-
重要性:
- 开闭原则对测试的影响: 一个方法的测试一般不少于三种, 正常的业务逻辑,边界,异常
- 开闭原则可以提高复用性
- 开闭原则可以提高可维护性
- 面向对象开发的要求
-
如何使用:
-
抽象约束:
- 通过接口或者抽象类约束扩展,对扩展进行边界设定, 不允许出现在接口不存在的public方法
- 参数类型,引用对象尽量使用接口或者抽象类, 恶如是实现类
- 抽象层尽量保持稳定, 一旦确定即不允许修改。
-
元数据(metadata)控制模块行为
- 使用最多就是spring容器
-
制定项目章程
-
封装变化
- 将相同的变化封装到一个接口或者抽象类种
- 将不同的变化封装到不同的接口或者抽象类种, 不应该有两个不同的变化出现在同一个接口或抽象类中。
-
-
最佳实践
- 开闭原则也只是一个原则:要灵活使用
- 项目规章非常重要
- 预知变化
-
有趣小知识点:
我们把价格定义为int类,在非金融类的项目中对货币处理时,一般取2位精度, 通常的设计方法时在运算过程中扩大100倍, 在需要展示时再缩小100倍,减少精度带来的误差。NumberFormat formatter = NumberFormat.getCurrencyInstance(); formatter.setMaximumFractionDigits(2); formatter.format(book.getPrice()/100.0);
-
-