软件设计的七大原则
软件设计原则
一、开闭原则
对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,想要达到这样的效果,需要使用接口和抽象类。
软件中易变的细节可以从抽象派生的实现类中进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类即可。
举例:
【例】搜狗输入法的皮肤设计
分析:皮肤是搜狗输入法(SouGouInput)的属性,用户可以根据自己的喜爱更换输入法的皮肤。这些皮肤有共同的特点,比如都由图片、输入窗口等组成。可以为其定义一个抽象类的皮肤(AbstractSkin),而每个具体的皮肤(DefaultSpecificSkin 和 HeimaSpecificSkin)是其子类。如下图所示:
如果有新的皮肤,只需要定义新的实现类即可,无需修改抽象皮肤类。
二、里氏代换原则
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,会导致整个继承体系的可复用性降低
举例:
【例】正方形属于长方形
在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形,如下图所示:
RectangleDemo
类是软件系统中的一个组件,它有一个 resize
方法依赖基类Rectangle,resize方法是RectandleDemo类中的一个方法,用来实现宽度逐渐增长直到大于长度的效果。
代码如下:
正方形(Square):
由于正方形的长和宽相同,所以在方法 setLength
和 setWidth
中,对长度和宽度都需要赋相同值:
public class Square extends Rectangle {
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) {
super.setLength(width);
super.setWidth(width);
}
}
RectangleDemo 类:
resize()
方法如下:
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() <= rectangle.getLength()) {
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
运行一下这段代码就会发现,假如把一个长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合预期。
假如把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长(正方形重写的 setWidth
方法将会使长宽一致),代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。
进行改进:
创建一个接口 Quadrilateral
,定义四边形通用的获取长宽的抽象方法,长方形和正方形分别实现这个接口,定义各自具体的设置长宽的方法,如下图:
此时,resize方法只可以计算长方形,无法计算正方形,解决了之前的问题。
三、依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
依赖:A类依赖B类,也就是在A类中声明了B类型的成员变量。
举例:
【例】组装电脑
现要组装一台电脑,需要配件cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常的运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿,海盗船等,类图如下:
Computer
类中直接依赖了具体的硬件:
这种方式最大的缺陷是,电脑的 cpu 只能是 Intel 的,内存条只能是金士顿的,硬盘只能是希捷的。
根据依赖倒转原则进行改进:
让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类,类图如下:
后期如果有不同品牌的产品,只需要实现对应的接口即可,无需修改Computer类。
四、接口隔离原则
- 一个类不应该*依赖于它不使用的方法:
- 一个类对另一个类的依赖应该建立在最小的接口上:
举例:
【例】安全门案例
有一个 HeiMa 品牌的安全门,该安全门具有防火、防水、防盗的功能。可以将防火,防水,防盗功能提取成一个接口,形成一套规范。类图如下:
现在如果还需要创建一个其他品牌的安全门,该安全门只具有防盗、防水功能,很显然如果实现 SafetyDoor
接口就违背了接口隔离原则,需要改进,将不同的功能提取到不同的接口,类图如下:
这样,不同的安全门,需要什么样的功能实现对应的接口即可。
五、迪米特法则
迪米特法则又叫最少知识原则。
如果两个软件实体之间无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性:
可以理解为,只和朋友交谈,不和陌生人交谈。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
举例:
【例】明星与经纪人的关系实例
明星的许多日常事务由经纪人负责处理,如和粉丝的见面会,和媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是明星的陌生人,所以适合使用迪米特法则,类图如下:
六、合成复用原则
尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
继承的缺点:
- 继承破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的。
- 子类与父类的耦合度高。父类的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
组合或聚合的优点:
- 对象间的耦合度低。可以在类的成员位置声明。
举例:
【例】汽车分类管理程序
汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下(继承复用):
从上面的类图可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。可以试着将继承复用改为聚合复用,如下图:
二者的对比,假设要新添加一种光能汽车类:
-
对于继承复用(添加了三个类)
-
对于聚合复用(仅添加了一个类)
七、单一职责原则
不要存在多于一个导致类变更的原因。
比如,Class
类有两个职责 a 和 b,a变更会导致类的变更,进而可能导致对职责b功能的影响;同理,b变更会导致类的变更,进而可能导致对职责a功能的影响。也就是说职责 a 和 b 都可以对类进行变更,不满足单一职责原则。
解决方案:一个类或接口只负责一项职责。
好处:降低类的复杂度,提高类的可读性、可维护性,降低变更带来的风险。
举例:
用一个类描述动物呼吸这个场景:
class Animal{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("牛");
}
}
运行结果:牛呼吸空气。
此时出现一个问题,并不是所有的动物都呼吸空气的,比如鱼就是呼吸水的。修改时如果遵循单一职责原则,需要将 Animal 类细分为陆生动物类 Terrestrial
,水生动物 Aquatic
,代码如下:
class Terrestrial{
public void breathe(String animal){
System.out.println(animal+"呼吸空气");
}
}
class Aquatic{
public void breathe(String animal){
System.out.println(animal+"呼吸水");
}
}
public class Client{
public static void main(String[] args){
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe("牛");
Aquatic aquatic = new Aquatic();
aquatic.breathe("鱼");
}
}
运行结果:牛呼吸空气,鱼呼吸水。
如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类 Animal 来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:
class Animal{
public void breathe(String animal){
if("鱼".equals(animal)){
System.out.println(animal+"呼吸水");
}else{
System.out.println(animal+"呼吸空气");
}
}
}
但是这种方式有一个缺点,动物类型越多,if 判断越多。
所以,是否使用单一职责原则,需要根据具体的情况来决定,比如上述,逻辑比较简单,可以违背单一职责原则。
上一篇: 初识laravel5,laravel5_PHP教程
下一篇: “SOLID“五大原则