SRP | The Single Responsibility Principle | 单一职责原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
一、单一职责原则(SRP)
从面向对象角度解释这个原则为:"引起类变化的因素永远不要多于一个。" 或者说 "一个类有且仅有一个职责"。
我们通常都说“低耦合,高内聚”。在我看来,这里的"单一职责"就是我们通常所说的“高内聚”,即一个类只完成它应该完成的职责,不能推诿责任,也不可越殂代疱,不能成为无所不能的上帝类。如果你的团队中实施宽松的“代码集体所有权”,在编码的过程中出现许多人同时修改(维护)同一个类的现象,而且成员之间的沟通不够及时,主动和畅通的话,那么时间一长,就很可能出现“承担过多职责”的上帝类。如果你有一个类承担了多项职责,那么你应该重新考虑下这个类,将它进行拆分。class Student {
public $studentName = '';
public $studentRegNo = '';
// Responsibility 1
function insertStudent($aStudent) {
// Logic for inserting a student to database.
}
function selectStudent($aStudent){
//logic
}
function deleteStudent($aStudent){
//logic
}
// Responsibility 2
function generateMarkSheet($aStudent) {
//logic for generating marks.
}
}
我们将这个类拆分开来,这样每个类就都满足单一职责了。
类1:Student 类2:StudentDB 类3:StudentReport
Student 负责get\set name,
StudentDB 负责:selectStudent、deleteStudent
StudentReport负责:generateMarkSheet
举例说明:
想必大家都写过服务器与客户端。他们之间的联系是很紧密的,若服务器端发生变化,很可能使客户端也要做修改,这就导致了无谓的麻烦。因为这样做违背了”开放封闭原则“的设计,导致了 他们之间的高耦合度。
若是改成下图:
这样子做添加了一个抽象服务类,而在客户端类中包含了一个抽象服务类的引用,具体的服务类实现了抽象服务类。这样,当具体的服务类发生修改时,抽象服务类没有改动,进而包含抽象服务类的引用的客户端类就不用发生改动!
在这里,抽象是关键!我觉得之前梳理的java动态代理就是基于这种接口模式。
接口分离原则的核心思想就是:不应该强迫客户程序依赖它们不需要使用的方法。 也就是说,一个接口或者类应该拥有尽可能少的行为(就是少到恰好能完成它自身的职责),这也是保证“软件系统模块的粒度尽可能少,以达到高度可重用的目的。
接口包含太多的方法会降低其可用性,像这种包含了无用方法的"胖接口"会增加类之间的耦合。如果一个类想实现该接口,那么它需要实现所有的方法,尽管有些对它来说可能完全没用,所以这样做会在系统中引入不必要的复杂度,降低代码的可维护性或鲁棒性。
例子可以参见上面的拆分fly接口。
五、依赖倒置原则(DIP)
这个原则的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象。其实又是”面向接口编程,不要面向实现编程“的内在要求。
们考虑一个现实中的例子,来看看依赖倒置原则给我们软件带来的好处。
你的汽车是由很多如引擎,车轮,空调和其它等部件组成,对吗?
注意:这里的 Car 就是高层模块;它依赖于抽象接口IToyotaEngine 和 IEighteenInchWheel.
而具体的引擎FifteenHundredCCEngine 属于底层模块,也依赖于抽象接口IToyotaEngine ;
具体的车轮 EighteenInchWheelWithAlloy同样属于底层模块,也依赖于抽象接口IEighteenInchWheel。
上面Car类有两个属性(引擎和车轮列表),它们都是抽象类型(接口)。引擎和车轮是可插拔的,因为汽车能接受任何实现了声明接口的对象,并且Car类不需要做任何改动。
再举个例子:门禁(第一步:必须先建立连接;第二部:发送传输命令,让门磁打开,或是关闭。第三步:关闭连接。)这里,我会将门禁的连接、断开封装在一个接口中。将发送命令封装在另外一个接口中。
这里是连接接口:
interface IConnect{
Connect();
Disconnect();
}
这里是通讯接口:
interface ISendCommand{
sendcommand();
}
这里是我们的门禁接口:
interface IDoor Extends IConnect,ISendCommand{}
我们可以用的Door来实现Idoor接口。如果这个时候硬件工程师告诉我们。通讯方式变了。这个时候。我们只需要添加一个新的类叫NewDoor用不同实现idoor接口。//旧设备
IDoor dr =
new
Door();
dr.Connect();
dr.SendCommand();
dr.DisConnect();
//新设备
IDoor ndr =
new
NewDoor();
ndr.Connect();
ndr.SendCommand();
ndr.DisConnect();
还有两个常被提到的原则:
六、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
合成复用原则(CARP),在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.
意思就是:在复用对象的时候,要优先考虑使用组合,而不是继承,这是因为在使用继承时,父类的任何改变都可能影响子类的行为,而在使用组合时,是通过获得对其他对象的引用而在运行时刻动态定义的,有助于保持每个类的单一职责原则。