设计模式——六大设计原则
文章目录
- 一、单一职责原则
- 二、里式替换原则
- 三、依赖倒置原则
- 四、接口隔离原则
- 五、迪米特法则
- 六、开闭原则
一、单一职责原则
单一职责原则简称 SRP,他想表达的就是字面意思,一个类只承担一个职责。
有时候我们可以将一个复杂的接口拆成两个不同的接口,这两个接口承担着不同的责任,这就是依赖了单一职责原则;它的定义就是:应该有且仅有一个原因引起类的变更。
关于 职责
的定义很模糊,什么才是职责呢?不同的人有不同的解读,所以该原则很难运用,需要开发者的慧眼。
下面以大学学生工作管理程序为例介绍单一职责原则的应用。
二、里式替换原则
里式替换原则也叫 LSP 原则,没错就是你想的那个 lsp 😝。
其实是英文简写啦,LSP 即 Liskov Substitution Principle
。
继承是非常优秀的语言机制,它的优点如下:
- 代码共享:父类共享给子类;
- 提高代码的可扩展性:很多框架的接口都是通过继承父类实现的;
缺点是增加了代码的耦合性,降低了代码的灵活性。
但是从整体上看,继承还是利大于弊的,我们想要将利最大化,就需要用到 里式替换
了。
里氏替换原则通俗来讲就是:
- 子类可以扩展父类的功能,但不能改变父类原有的功能;
- 只要父类出现的地方子类就可以出现,而且替换为子类不会出现任何错误。
三、依赖倒置原则
依赖倒置原则的 3 个含义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
- 抽象不应该依赖细节;
- 细节应该依赖抽象;
这里的抽象指的就是 接口或抽象类
,细节指的是 实现类
。
其核心思想是:要面向接口编程,不要面向实现编程。
为什么面向接口呢?面向接口就是面向抽象,由于在软件设计中,细节具有多变性,而 抽象层
则 相对稳定 ,因此以抽象为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。
依赖倒置原则在 Java 中的表现是:
- 模块之间的依赖通过抽象产生,实现类之间不能发生依赖关系,只能通过接口或抽象类产生;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类;
我们以一个例子来看一下依赖倒置原则的重要性:
现在有一个司机,他想开一辆奔驰车。
代码可以这样写:
司机:
public class Driver() {public void drive(Benz benz) {benz.run();}}
奔驰车:
public class Benz() {public void run() {System.out.println("奔驰汽车开始运行...");}}
主函数:
public class Client() {public static void main() {Driver zhangSan = new Driver();Benz benz = new Benz();// 张三开奔驰车zhangSan.drive(benz);}}
这样运行起来,可以得到我们想要的结果,代码没有问题。
但是只有当需求变更的时候才能真正的检验代码的质量。
现在题目要求司机开宝马车,怎么改?
我们先创建出来宝马车:
public class BMW() {public void run() {System.out.println("宝马汽车开始运行...");}}
宝马车出来了,但是我们却没办法让张三将车开动起来,为什么?因为张三没有开动宝马车的方法啊。
此时我们发现我们的系统出现了问题:司机和奔驰车之间是紧耦合的关系,司机是实体类,奔驰车也是实体类,他们两个发生了依赖关系,违背了我们说的依赖倒置原则。
那怎么修改呢?
既然是面向接口,那我们就创建两个接口,让抽象的接口互相依赖:
下面介绍 3 种依赖的类型。
依赖的 3 种写法
依赖是可以传递的,A 对象依赖 B 对象,B 又依赖 C,C 又依赖 D······,但是只要我们做到抽象依赖,即使是多层的依赖传递也不怕。
对象的依赖关系有三种方式来传递,分别是:
1、构造函数传递依赖对象
public class Driver() {public void drive(Benz benz) {benz.run();}}
2、Setter 方法传递依赖对象
public interface IDriver {public void setCar(ICar car);public void drive();}
3、接口声明依赖对象
在接口的方法中声明依赖对象,也叫作接口注入。
public interface IDriver {public void driver(ICar car);}
那么我们在项目中怎么使用这个规则呢?可以从以下几个方面入手:
- 每个类尽量都要有接口或抽象类;
- 变量的表面类型尽量是接口或者抽象类;
- 任何类都不应该从具体类派生;
- 集合里式替换原则使用。
我们可以得出一个通俗的规则:
- 接口负责定义 public 属性和方法,并且声明与其他对象的依赖关系;
- 抽象类负责公共构造部分的实现;
- 实现类准确的实现业务逻辑,同时对父类进行细化。
四、接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
就是要让接口中的方法尽可能的少而精。
同时注意,根据接口隔离原则拆分接口时,首先必须满足 单一职责原则
,不能无限拆分。
例题:学生成绩管理程序。
分析:学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、査询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等 3 个模块中,其类图如图所示。
五、迪米特法则
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP)。
一个类应该对自己需要耦合或调用的类知道的最少,你的内部是如何复杂都和我没有关系,那是你的事情,我就知道你提供那么多 public
方法,我就调用这么多,其他的我一概不关心。
先说一下什么是 朋友类 :
- 出现在成员变量、方法的输入输出参数中的类称为成员朋友类;
- 出现在方法体内部的类不属于朋友类。
迪米特法则的目的就是低耦合,它包含 4 层含义:
1、只和朋友交流
也就是说符合迪米特法则的类中的方法,应该不能出现非朋友类,不能和陌生人有交流。
2、朋友之间也是有距离的
人与人之间是有距离的,类与类之间也有距离,不能太过亲密。
比如 A 类有 3 个方法,被 B 类的一个方法 m1
全部调用了,这样一来就会有一个问题,当 A 类修改方法的名称时,B 类也要修改 m1
方法,这就是耦合太紧了。
解决办法就是将 m1
方法转移到 A 类中去,让 B 类调用 A 的 m1
方法,这样就是高内聚低耦合了。
同时 A 类中的 3 个方法可以设置成私有的,因为只有他自己调用,只需要将 m1
设置成 public
就可以了。
一个类公开的 public
属性或方法越多,修改时涉及的面也就越大,迪米特法则要求类 羞涩
一点,尽量内敛。
3、是自己的就是自己的
如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
4、谨慎使用 Serializable
明星与经纪人的关系实例。
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如图所示。
六、开闭原则
开闭原则是 Java 世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。
他要求软件实体应该对扩展开放,对修改关闭。
这里的软件实体包括:
- 模块;
- 抽象和类;
- 方法。
前面提到的几个原则都是开闭原则的具体形态,也就是说前五个原则就是指导设计的工具和方法,而开闭原则才是精神领袖。
开闭原则
在面向对象设计领域中的地位类似于牛顿第一定律在力学中的地位。
下面以 Windows 的桌面主题为例介绍开闭原则的应用。
例题:Windows 的桌面主题设计。
分析:Windows 的主题是桌面背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的桌面主题,也可以从网上下载新的主题。这些主题有共同的特点,可以为其定义一个抽象类(Abstract Subject),而每个具体的主题(Specific Subject)是其子类。用户窗体可以根据需要选择或者增加新的主题,而不需要修改原代码,所以它是满足开闭原则的,其类图如图所示。
软件设计最大的难题就是应对需求的变化,但是繁杂的需求变化又是不可预料的。我们要为不可预料的事情做好准备,大师们为我们提供了 6 大设计原则和 23 种设计模式来 封装
未来的变化。来源:https://www.icode9.com/content-4-793951.html