一、封装变化
我们应当找出应用中可能出现变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这样更利于以后的代码重构。
二、多用组合,少用继承
当我们希望一个类A拥有另一个类B的特性时,可以尝试在A中声明一个B类的引用,在A中所有的B类的功能实现,都交给这个B类引用来实现。当然继承也可以实现这个功能、,可以根据具体场景来选择。使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
三、针对接口编程,而不是针对实现编程。
// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark();
// 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound();
// 更棒的是,子类实例化的动作不再需要在代码中硬编码,例如new Dog(),
//而是“在运行时才指定具体实现的对象”。
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();
四、松耦合原则
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者具体类是谁、做了些什么或其他任何细节。主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者和删除观察者。有新类型的观察者出现时,主题的代码不需要修改。当有新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为两者并非紧耦合。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降低到了最低。
五、要依赖抽象,不要依赖具体类
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,两者都应该依赖于抽象。这也就是依赖倒置原则的另一种阐述。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
1、变量不可以持有具体类的引用。
如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。
2、不要让类派生自具体类。
如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类)。
3、不要覆盖基类中已实现的方法。
如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
要完全遵守这些指导方针似乎不太可能,但是如果你深入体验这些方针,将这些方针内化成你思考的一部分,那么在设计时,你将知道何时有足够的理由违反这样的原则。比方说,如果有一个不像是会改变的类,那么在代码中直接实例化具体类也就没什么大碍。另一方面,如果有个类可能改变,你可以采用一些好技巧(例如工厂方法)来封装改变。
六、最少知识原则
当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统会变成一个易碎的系统,它需要花许多成本维护,也会因为太复杂而不容易被其他人了解。
究竟要怎样才能避免这样呢?这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
1、该对象本身;
2、被当做方法的参数而传递进来的对象;
3、此方法所创建或实例化的任何对象;
4、对象的任何组件。
如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了(让我们的朋友圈子维持在最小的状态)。比方说:
// 这里,我们从气象站取得了温度计对象,然后再从温度计对象取得温度
public float getTemp(){
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
// 在应用最少知识原则时,我们在气象站中加进一个方法,用来向温度计请求温度。
// 这可以减少我们所依赖的类的数目
public float getTemp(){
return station.getTemperature();
}
七、好莱坞原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”。
好莱坞原则和模板方法之间的连接其实还算明显:当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。怎样才能办到呢?让我们再看一次咖啡因饮料的设计:
1. CaffeineBeverage是我们的高层组件,它能够控制冲泡法的算法,只有在需要子类实现某个方法时,才调用子类。
2. 饮料的客户代码只依赖CaffeineBeverage抽象,而不依赖具体的Tea或Coffee,这可以减少整个系统的依赖。
3. Tea和Coffee子类只简单提供brew()和addCondiments()方法的实现细节。如果Tea和Coffee没有先被调用,绝对不会直接调用抽象类。
八、单一责任
一个类应该只有一个引起变化的原因。
当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。两个?没错,就是两个!如果这个集合改变的话,这个类也必须改变,如果我们遍历的方式改变的话,这个类也必须跟着改变。
我们知道要避免类内的改变,因为修改代码很容易造成许多潜在的错误。如果有一个类具有两个改变的原因,那么这会使得将来该类的变化几率上升,而当它真的改变时,你的设计中同时有两个方面将会受到影响。
我们应当找出应用中可能出现变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这样更利于以后的代码重构。
二、多用组合,少用继承
当我们希望一个类A拥有另一个类B的特性时,可以尝试在A中声明一个B类的引用,在A中所有的B类的功能实现,都交给这个B类引用来实现。当然继承也可以实现这个功能、,可以根据具体场景来选择。使用组合建立系统具有很大的弹性,不仅可将算法封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
三、针对接口编程,而不是针对实现编程。
// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark();
// 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound();
// 更棒的是,子类实例化的动作不再需要在代码中硬编码,例如new Dog(),
//而是“在运行时才指定具体实现的对象”。
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();
四、松耦合原则
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者具体类是谁、做了些什么或其他任何细节。主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者和删除观察者。有新类型的观察者出现时,主题的代码不需要修改。当有新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为两者并非紧耦合。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降低到了最低。
五、要依赖抽象,不要依赖具体类
这个原则说明了:不能让高层组件依赖低层组件,而且,不管高层或低层组件,两者都应该依赖于抽象。这也就是依赖倒置原则的另一种阐述。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
1、变量不可以持有具体类的引用。
如果使用new,就会持有具体类的引用。你可以改用工厂来避开这样的做法。
2、不要让类派生自具体类。
如果派生自具体类,你就会依赖具体类。请派生自一个抽象(接口或抽象类)。
3、不要覆盖基类中已实现的方法。
如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有的子类共享。
要完全遵守这些指导方针似乎不太可能,但是如果你深入体验这些方针,将这些方针内化成你思考的一部分,那么在设计时,你将知道何时有足够的理由违反这样的原则。比方说,如果有一个不像是会改变的类,那么在代码中直接实例化具体类也就没什么大碍。另一方面,如果有个类可能改变,你可以采用一些好技巧(例如工厂方法)来封装改变。
六、最少知识原则
当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统会变成一个易碎的系统,它需要花许多成本维护,也会因为太复杂而不容易被其他人了解。
究竟要怎样才能避免这样呢?这个原则提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
1、该对象本身;
2、被当做方法的参数而传递进来的对象;
3、此方法所创建或实例化的任何对象;
4、对象的任何组件。
如果调用从另一个调用中返回的对象的方法,会有什么害处呢?如果我们这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了(让我们的朋友圈子维持在最小的状态)。比方说:
// 这里,我们从气象站取得了温度计对象,然后再从温度计对象取得温度
public float getTemp(){
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
// 在应用最少知识原则时,我们在气象站中加进一个方法,用来向温度计请求温度。
// 这可以减少我们所依赖的类的数目
public float getTemp(){
return station.getTemperature();
}
七、好莱坞原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”。
好莱坞原则和模板方法之间的连接其实还算明显:当我们设计模板方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。怎样才能办到呢?让我们再看一次咖啡因饮料的设计:
1. CaffeineBeverage是我们的高层组件,它能够控制冲泡法的算法,只有在需要子类实现某个方法时,才调用子类。
2. 饮料的客户代码只依赖CaffeineBeverage抽象,而不依赖具体的Tea或Coffee,这可以减少整个系统的依赖。
3. Tea和Coffee子类只简单提供brew()和addCondiments()方法的实现细节。如果Tea和Coffee没有先被调用,绝对不会直接调用抽象类。
八、单一责任
一个类应该只有一个引起变化的原因。
当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。两个?没错,就是两个!如果这个集合改变的话,这个类也必须改变,如果我们遍历的方式改变的话,这个类也必须跟着改变。
我们知道要避免类内的改变,因为修改代码很容易造成许多潜在的错误。如果有一个类具有两个改变的原因,那么这会使得将来该类的变化几率上升,而当它真的改变时,你的设计中同时有两个方面将会受到影响。
没错,这听起来很容易,但其实做起来并不简单:区分设计中的责任,是最困难的事情之一。我们的大脑很习惯看着一大群的行为,然后将它们集中在一起,尽管他们可能属于两个或者多个不同的责任。想要成功的唯一方法,就是努力不懈地检查你的设计,随着系统的成长,随时观察有没有迹象显示某个类改变的原因超出一个。