一:封装变化
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
- 把会变化的部分去除并“封装”起来,好让其他部分不会受到影响。结果如何?代码变化引起的不经意后果变少,系统变得更有弹性。
- 换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那马你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。
另一种思考方式:把会变化的部分取出并封装起来,以便以后可以轻易的改动或扩充此部分,而不影响不需要变化的其他部分。
二:多用组合,少用继承
当你将两个类结合起来使用,这就是组合(composition)。这种做法和继承不同的地方在于,对象的行为不是继承来的,而是和适当的行为对象组合来的。
使用组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
三:针对接口编程,而不是针对实现编程
“针对接口编程”真正的意思是“针对超类型(supertype)编程”。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。
- “针对超类型编程”这句话,可以更明确的说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型。”
- 对象的行为将被放在分开的类中,此类专门提供某行为接口的实现。这样,类就不再需要知道行为的实现细节。 ```dart
/// 抽象超类型可以是抽象类或接口 abstract class Animal { void makeSound(); }
/// 具体实现 class Dog extends Animal { @override void makeSound() { this.bark(); }
void bark() {
print('汪汪叫');
} }
class Cat extends Animal { @override void makeSound() { this.meow(); }
void meow() { print(‘喵喵叫’); } }
/// 针对实现编程 /// 生命变量 dog 为Dog类型(Animal 的具体实现),会造成我们必须针对具体实现编程 Dog dog = new Dog(); dog.bark();
/// 针对接口/超类编程 /// 我们知道该对象时狗,但是我们现在利用animal进行多态的调用 Animal animal = new Dog(); animal.makeSound();
/// 子类的动作不再需要在代码中硬编码,在运行时指定具体实现的对象 var a = getAnimal(); a.makeSoiund(); /// 我们不知道实际的子类型是什么,我们只关心它知道如何正确地进行makeSound()的动作就够了。
四:为了交互对象之间的松耦合设计而努力
- 当两个对象之间松耦合,他们依然可以交互,但是不大清楚彼此的细节。
- 松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
-
五:开放-关闭原则:类应该对扩展开放,对修改关闭
我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如果能实现这样的目标,有什么好处呢?这样的设计具有弹性可以应对改变,可以接受新的功能来应对改变的需求。
- 虽然似乎有点矛盾,但是的确有一些技术可以允许在不直接修改代码的情况下对其进行扩展。
- 在选择需要被扩展的代码部分时要小心。每个地方都采用开放-关闭原则,是一种浪费,也没有必要,还会导致代码变的复杂且难以理解。
六:依赖抽象,不要依赖具体类
七:只和朋友交谈
八:别找我,我会找你
九:单一职责:类应该只有一个改变的理由