导读:目录.封装变化.针对接口编程,不针对实现编程.多用组合(has-a),少用继承(is-a).为交互对象之间的松耦合设计而努力.最少知识原则 LKP / 迪米特法则 Law of Demeter.好莱坞原则.SOLID 原则.单一职责原则 SRP.开放关闭原则 OCP.里氏替代原则
找出应用中需要经常变化的部分,把他们独立出来,改变这部分代码不影响其他部分。这几乎是每个设计模式背后的精神所在,即系统中某部分的改变不影响其他部分。
针对接口编程,关键在于多态 。变量/成员/形参的声明应该是抽象类/接口类/父类,即所有的代码操作的都是父类/接口类/抽象类(如 Animal),只会在一处会涉及到具体类(如 Cat 或 Dog),那就是在用 new 实例化具体子类对象时,而这部分代码最好也用工厂封装起来,这样甚至可以在运行时动态实例化不同的子类对象。
针对实现编程 👎 | 针对接口编程 👍 | 针对接口编程 + 工厂 👍👍 |
---|
Dog d = new Dog();
d.bark(); | Animal a = new Dog();
a.makeSound(); | Animal a = getAnimal();
a.makeSound();
继承和组合都可以实现代码复用,但组合比继承更灵活,可运行时改变行为/属性。例如下面的游戏战车,采用组合可以在运行时动态地通过 setWeapon()
来切换武器,而继承是在编译时静态决定子类行为,则没有这种灵活性。
继承(is-a) 👎 | 组合(has-a) 👍 |
---|---|
![]() |
![]() |
例如观察者模式使得观察者(Listener)和被观察者(Subject)之间是松耦合的。被观察者(Subject)不知道观察者的细节,只知道观察者实现了
Listener 接口(如 update()
),只要他们之间的接口不变,就可以自由地改变一方而不影响另一方。
最少知识原则(LKP, Least Knowledge Principle)也被称为迪米特法则(Law of Demeter)。其目的是减少对象之间的交互和类之间的依赖,不要让太多的类耦合在一起,避免修改一部分影响到另一部分。
为了避免依赖过多的类,在 Foo
类的方法 func(Bar bar)
中只应该调用:
Foo
类自身的方法/成员函数Foo
类成员的方法bar
的方法func()
中创建的对象的方法应避免调用由另一个方法返回的对象的方法,即避免像下面这样连续的函数调用:
// 👎 依赖 Station、Thermometer 类
float getTemp() {
return station.getThermometer().getTemperature();
}
// 👍 只依赖 Station 类
float getTemp() {
return station.getTemperature();
}
外观模式是该原则的体现。Facade 封装了复杂的子系统,提供了统一、简单的高层接口, Client 只和 Facade 进行交互,避免了 Client 和子系统的紧耦合。
缺点:需要额外的包装类,增加复杂度,降低运行时性能。
允许低层组件将自己挂钩(hook)到系统上,但是高层组件决定什么时候和怎样使用这些低层组件(即高层组件对待低层组件的方式是:Don’t call me, I’ll call you)。一个典型例子是模版方法模式:
class Drink {
public:
void makeDrink() final
{
boilWater();
brew();
pourInCup();
addCondiments();
}
void boilWater() {...}
virtual void brew() = 0;
void pourInCup() {...}
virtual void addCondiments() = 0;
};
高层组件 Drink 的 makeDrink()
方法控制着饮料制作方法流程。低层组件 Tea/Coffee 通过 override
brew()/addCondiments()
方法可以将自己 hook
到系统上,从而改变最终的行为。高层组件控制着何时、如何让低层组件参与,低层组件不可以直接调用高层组件。客人只依赖 Drink 类,而不依赖具体的
Tea/Coffee 类。
此外,工厂方法、观察者等模式也遵循了该原则:如工厂方法子类决定如何创建具体对象、低层的 Listener 把自己添加到高层被观察者的 Listener 列表中等。特别地,标准库中的很多算法也是该原则的体现:例如用标准库 sort 算法对自定义类的对象进行排序,需要传入一个可调用对象(比较函数/lambda)。
思考:和依赖倒置原则的关系?
这是最著名的 5 个设计原则,在《整洁架构之道》的第三部分的学习笔记中再次详细介绍这 5 个原则。
Just because you can doesn’t mean you should.
类应该只有一个改变的理由。这背后的原因是每次修改代码可能产生潜在的错误。一个类只做一件事的时候,更容易实现高内聚;反之,如果一个类支持多个不相关的功能时,通常是低内聚的。当项目中经常由于不同的原因修改一个类的时候,或者负责不同业务的两个团队经常需要修改同一个文件,则是时候考虑单一职责原则了。
类应该对扩展开放,对修改关闭。扩展新功能只需新增 代码,而不需要修改 现有代码,减少 code review 的工作量及意外引入 bug 的可能性。
需要父类的地方都可以用子类替代,背后的原因是希望实现同一接口的组件之间可以任意替换。
一个接口中有几十个方法,但客户 A。可能只需要其中的几个方法,而另一个客户 B 可能需要另外几个方法。臃肿的接口使得两个客户之间产生耦合,为客户 A 修改的某些方法也会影响到客户 B,即使客户 B 并没有用到这些方法。
依赖倒置原则的另一种表达方式是:要依赖抽象,不要依赖具体类 。和“针对接口编程, 不针对实现编程”很相似,但这里更强调抽象。高层策略性代码不应该依赖低层实现细节代码,两者都应该依赖抽象。抽象也可以看作是高层策略的一部分,即低层细节实现应该依赖高层。控制流方向往往都是从高层到低层,而依赖倒置,是将源码的依赖关系反转,在源码级别上使得低层代码依赖高层策略。这一点对于软件架构来说尤为重要,《架构整洁之道》的第三部分深入地讨论了这一点。
Don’t Repeat Yourself 不要重复。重复是一切邪恶的根源。许多原则、最佳实践都是为了消除重复。
Keep It Simple and Stupid 保持简单。不要本末倒置,简单的事情简单做!不要过度设计,不要把简单的事情搞复杂!
上一篇:读书(五)--《唐诗鉴赏(2)》
下一篇:《深度学习入门·基于Python