参考链接:https://www.imooc.com/read/53/article/1079
1、基本概念
本节继续介绍设计模式的七大原则的基本概念,上一节重要讲了开闭原则、单一职责原则、里氏替换原则、依赖倒置原则,这一节我们主要了解下接口隔离原则、迪米特法则以及合成复用原则。
本节以介绍基本概念为主,其中会加入部分演示代码、uml 类图讲解,能理解基本概念即可。后续章节设计模式的讲解会详细介绍这些原则的应用。
本节主要内容有:
- 什么是接口隔离原则、迪米特法则以及合成复用原则
- 为何要遵循这些原则
2、接口隔离原则
接口隔离原则(Interface Segregation Principle,ISP)的定义是客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上。 简单来说就是建立单一的接口,不要建立臃肿庞大的接口。也就是接口尽量细化,同时接口中的方法尽量少,保持接口纯洁性。
我们所讲的接口主要分为两大类,一是实例接口,比如使用 new 关键字产生一种实例,被 new 的类就是实例类的接口。从这个角度出发的话,java 中的类其实也是一种接口。二是类接口,java 中常常使用 interface 关键字定义。
举个栗子来说,我们使用接口 IPrettyGirl 来描述美女,刚开始类图可能描述如下:

但是发现该接口中包含对美女的外观描述、内在美描述等,几乎将美女的所有特性全部纳入,这显然不是一个很好的设计规范,比如在唐朝,在那个以丰腴为美的时代对美的理解就不同,就会出现单纯 goodLooking 过关就是美女的结果,所以这里我们需要将接口隔离拆分。将一个接口拓展为两个,增加系统灵活性及可维护性。
这里我们将美女接口拆分为内在美、外在美两个接口,系统灵活性提高了,另外接口间还能使用继承实现聚合,系统拓展性也得到了增强。
接口隔离原则总结:
- 接口尽量粒度化,保持接口纯洁性
- 接口要高内聚,即减少对外交互
3、迪米特法则
迪米特法则(Law of Demeter,LOD),有时候也叫做最少知识原则(Least Knowledge Principle,LKP),它的定义是:一个软件实体应当尽可能少地与其它实体发生相互作用。迪米特法则的初衷在于降低类之间的耦合。
举个栗子,拿教师点名来讲,体育老师需要清点班上学生人数,教师一般不是自己亲自去数,而是委托组长或班长等人去清点,即教师通过下达命令至班长要求清点人数:
public class Girl {
}
public class GroupLeader {
private final List<Girl> girls;
public GroupLeader(List<Girl> girls) {
this.girls = girls;
}
public void countGirls() {
System.out.println("The sum of girls is " + girls.size());
}
}
public class Teacher {
public void command(GroupLeader leader){
leader.countGirls();
}
}
public class Main {
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher();
GroupLeader groupLeader = new GroupLeader(Arrays.asList(new Girl(), new Girl()));
teacher.command(groupLeader);
}
}
上述例子中,如果去掉 GroupLeader 这个中间人角色,教师就会直接去清点人数,这样做会违反迪米特法则。
迪米特法则总结:
- 类定义时尽量内敛,少使用 public 权限修饰符,尽量使用 private、protected 等。
4、合成复用原则
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。 原则是尽量首先使用合成 / 聚合的方式,而不是使用继承。
合成和聚合都是关联的特殊种类。合成是值的聚合(Aggregation by Value),而复合是引用的聚合(Aggregation by Reference)。
都知道,类之间有三种基本关系,分别是:关联(聚合和组合)、泛化(与继承同一概念)、依赖。
这里我们提一下关联关系,客观来讲,大千世界中的两个实体之间总是有着千丝万缕的关系,归纳到软件系统中就是两个类之间必然存在关联关系。如果一个类单向依赖另一个类,那么它们之间就是单向关联。如果彼此依赖,则为相互依赖,即双向关联。
关联关系包括两种特例:聚合和组合。聚合,用来表示整体与部分的关系或者 “拥有” 关系。其中,代表部分的对象可能会被代表多个整体的对象所拥有,但是并不一定会随着整体对象的销毁而销毁,部分的生命周期可能会超越整体。好比班级和学生,班级销毁或解散后学生还是存在的,学生可以继续存在某个培训机构或步入社会,生命周期不同于班级甚至大于班级。
合成,用来表示一种强得多的 “拥有” 关系。其中,部分和整体的生命周期是一致的,一个合成的新的对象完全拥有对其组成部分的支配权,包括创建和泯灭。好比人的各个器官组成人一样,一旦某个器官衰竭,人也不复存在,这是一种 “强” 关联。
如上图所示,Heart 和 Student、Teacher 都是一种 “强” 关联,人不能摆脱心脏而存在,即组合关系,而 Student 和 Class、School 是一种 “弱” 关联,脱离了学校、班级,学生还能属于社会或其他团体,即聚合关系。
合成复用原则总结:
- 新对象可以调用已有对象的功能,从而达到对象复用
5、总结
总结下这两节的内容,我们一共介绍了 7 种设计原则,它们分别为开闭原则、单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则和本节所介绍的合成复用原则。
各种原则要求的侧重点不同,总地来说:
1、开闭原则是核心,对拓展开放对修改关闭是软件设计、后期拓展的基石;
2、单一职责原则就要求我们设计接口,制定模块功能时保持模块或者接口功能单一,接口设计或功能设计尽量保持原子性,修改一处不能影响全局或其它模块;
3、里氏替换原则和依赖倒置原则,按照作者的理解,这俩原则总的是要求我们要面向接口、面向抽象编程,设计程序的时候尽可能使用基类或者接口进行对象的定义或引用,而不是具体的实现,否则实现一旦有变更,上层调用者就必须做出对应变更,这样一来,整个模块可能都需要重新调整,非常不利于后期拓展。
4、接口隔离原则具体应用到程序中,比如我们在传统 mvc 开发时,service 层调用 dao 层一般会使用接口进行调用,各层之间尽量面向接口通信,其实也是一种降低模块耦合的方法;
5、迪米特法则的初衷也是为了降低模块耦合,代码示例中我们引入了类似 “中间人” 的概念,上层模块不直接调用下层模块,而是引入第三方进行代办,这也是为了降低模块的耦合度;
6、合成复用原则一节,我们介绍了聚合、组合的概念,聚合是一种弱关联,而组合是一种强关联,表现在 UML 类图上的话聚合是使用空心四边形加箭头表示,而组合是使用实心四边形加箭头表示,合成复用原则总的就是要求我们尽量利用好已有对象,从而达到功能复用,具体是聚合还是组合,还是一般关联,就要看具体情况再定了。
这 7 种设计原则是软件设计必须尽量遵循的原则。