单一责任原则
单一责任原则规定:
一个班级应该只有一个责任。
类使用其函数或契约(以及数据成员帮助函数)来履行其职责。
以下面的示例类为例:
ClassSimulation{
PublicLoadSimulationFile()
PublicSimulate()
PublicConvertParams()
}
这个类处理两个责任。首先,这个类正在加载模拟数据,其次,它正在执行模拟算法(使用Simulate和ConvertParams 职能)。
类使用一个或多个函数来履行职责。在上面的示例中,加载模拟数据是一项责任,而执行模拟则是另一项责任。加载模拟数据需要一个函数(即LoadSimationFile)。其余两个函数需要执行模拟。
我怎么知道我们班有多少责任?把“改变的理由”看作类似于责任。因此,寻找类更改的所有原因。如果有一个以上的原因改变一个类,那么这意味着这个类不遵循单一责任原则。
在上面的示例类中,这个类不应该包含LoadSimulationFile 函数(或加载模拟数据责任)。如果我们创建一个单独的类来加载模拟数据,那么这个类就不会违反SRP。
一个类只能有一个责任。你怎么能用这么严格的规则来设计软件呢?
让我们考虑另一个与SRP密切相关的原则:高内聚力。高内聚力给你一个主观的尺度,而不是一个客观的尺度,例如SRP。很低的凝聚力意味着一个班级正在履行许多责任。例如,一个类负责的责任超过10项。低衔接意味着一个班级大约完成5个责任,适度衔接意味着一个班级完成3个责任。高凝聚力意味着一个班级在履行一个单一的责任。因此,设计时的经验法则是争取高度的凝聚力。
这里应该讨论的另一个原则是低耦合。这一原则规定,人们应该分配责任,以使类之间的依赖性保持在较低水平。再次考虑上面的示例类。在应用SRP和高内聚原理后,我们决定建立一个单独的类来处理仿真文件。通过这种方式,我们创建了两个相互依赖的类。
似乎应用高内聚力使我们违背了低耦合的原则。这个级别的耦合被允许作为目标,以最小化耦合,但不允许零联轴器。在创建面向对象的设计时,某种程度的耦合是正常的,在这种设计中,任务是通过对象的协作来完成的。
另一方面,考虑一个GUI类,它连接到数据库,通过HTTP处理远程客户端,并处理屏幕布局。这个GUI类依赖于太多的类。这个GUI类显然违反了低耦合原则。如果不涉及所有相关类,则不能重用此类。对数据库组件的任何更改都会导致GUI类的更改。
开闭原理
“开放-封闭原则”规定:
软件模块(可以是类或方法)应该开放供扩展,但关闭以进行修改。
换句话说,您不能更新已经为项目编写的代码,但可以向项目添加新代码。
有两种方法可以应用开闭原理.您可以通过继承或组合应用此原则。
下面是使用继承应用开放关闭原则的示例:
ClassDataStream{
Publicbyte[]Read()
}
ClassNetworkDataStream:DataStream{
Publicbyte[]Read(){
//Read from the network
}
}
ClassClient{
PublicvoidReadData(DataStreamds){
ds.Read();
}
}
在本例中,客户端读取数据(ds.Read())来自网络流。如果我想扩展客户机类的功能以从另一个流(例如pci数据流)读取数据,那么我将加类的另一个子类。DataStream类,如下面的清单所示:
ClassPCIDataStream:DataStream{
Publcbyte[]Read(){
//Read data from PCI
}
}
在这种情况下,客户端代码将在没有任何错误的情况下工作。客户端类知道基类,并且我可以传递以下两个子类中任何一个的对象DataStream。这样,客户端就可以在不知道底层子类的情况下读取数据。这是在没有改性任何现存的密码。
我们可以使用组合来应用这个原则,还有其他的方法和设计模式来应用这个原则。本文将讨论这些方法中的一些。
您是否必须将此原则应用于您编写的每一段代码?没有。这是因为大部分代码不会改变。您只需要在那些您怀疑代码将来会发生变化的情况下,战略性地应用这个原则。
Liskov代换原理
Liskov替代原则规定:
派生类必须是它们的基类的替代类。
另一种看待这个定义的方法是抽象(接口或抽象类)应该是足够的为了一个客户。
为了详细说明,让我们考虑一个例子,下面是一个接口,其清单如下:
PublicInterfaceIDevice{
VoidOpen();
VoidRead();
VoidClose();
}
此代码表示数据采集设备的抽象。数据采集设备因其接口类型不同而不同。数据采集设备可以使用USB接口、网络接口(TCP或UDP)、PCI快速接口或任何其他计算机接口。然而,iDevice的客户端不需要知道他们使用的是哪种设备。这使程序员在不改变依赖iDevice接口的代码的情况下,能够灵活地适应新设备。
当只有两个实现iDevice接口的具体类时,让我们来了解一下历史,如下所示:
publicclassPCIDevice:IDevice{
publicvoidOpen(){
// Device specific opening logic
}
publicvoidRead(){
// Reading logic specifi