5、接口隔离原则(Interface Segregation Principle, ISP)

定义

Clients should not be forced to depend upon interfaces that they don’t use.
客户端不应该依赖那些它不需要的接口

核心思想

记得几年前有一位很厉害的前辈说过:软件设计是什么,就是“分离关注点,消除重复”。这句话一直影响这我,而我做软件设计也是朝着这两个方向努力。而接口隔离原则最核心的就是拆分,即分离关注点。

实例

下面通过一个例子来说明接口隔离的好处。

接口隔离实例

有一个动物的接口,有三个实现类,分别是Dog(狗)、Swift(雨燕)、Carp(鲤鱼),伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface Animal {
/**
* 呼吸,所有动物都需要呼吸
*/
void breathe();
}

public class Dog implements Animal {
public void breathe() {
System.out.println("狗用肺呼吸");
}
}

public class Swift implements Animal {
public void breathe() {
System.out.println("雨燕用肺呼吸");
}
}

public class Carp implements Animal {
public void breathe() {
System.out.println("鲤鱼用腮呼吸");
}
}

现在新的需求来了,要增加动物的行动能力。狗要能跑,雨燕要能飞,鲤鱼要能游动。如果不拆分接口,那么接口将变成

接口隔离实例-反例

这里的需求是实现了,但是Dog类不得不实现fly()swim()方法,其他类也是类似的情况,它们不得不实现它们不具备的能力。Animal类就是一个大而全的接口,所有的东西具有的特性都在这里,但Animal的子类却不一定都具备这些特性。

这样做的主要弊端有两点:

  • 子类或者实现类不得不实现它们不需要实现的接口
  • 调用者不清楚哪些方法是不可用的。比如调用了Dogfly()方法,显然不能得到一个正确的结果,因为Dog不具备这样的能力。

下面提供一个Dog类的伪代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Dog implements Animal {
public void breathe() {
System.out.println("狗用肺呼吸");
}

public void run() {
System.out.println("狗子跑的飞快");
}

public void fly() {
throw new NotSupportedException("狗不具备飞翔的能力")
}

public void swim() {
System.out.println("狗不会游泳");
}
}

为了解决这个问题,我们将接口做一下拆分

接口隔离实例-优化

这样做就简单明了了。

1
2
3
4
5
6
7
8
9
public class Dog implements Animal, Runable {
public void breathe() {
System.out.println("狗用肺呼吸");
}

public void run() {
System.out.println("狗子跑的飞快");
}
}

我们将行动能力进行了拆分,拆成了RunableFlyableSwimable。如果想让动物们具备某种能力,只需要实现对应的接口即可。

比如现在狗子通过简单的训练,具备了游泳的能力(为什么是游泳?因为你无论怎么训狗子它都上不了天)。我们只需要实现Swimable接口即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Dog implements Animal, Runable, Swimable {
public void breathe() {
System.out.println("狗用肺呼吸");
}

public void run() {
System.out.println("狗子跑的飞快");
}

public void swim() {
System.out.println("狗子通过训练学会了游泳");
}
}

一个更复杂的例子

接口隔离复杂实例

从这张类图中我们可以看出,我们在动物的基础上增加了CanidaeBridFishAmphibian,分别是犬科、鸟类、鱼类、两栖动物。

这种设计方式是被允许的,而且也享受到了ISP带来的好处。比如两栖动物,它是动物而且具备跑和游泳的能力,这是所有两栖动物的特性(目前还没有即会游泳又会飞的两栖动物)。所以我们只需要让两栖动物的接口Amphibian继承RunableSwimable接口即可。这种设计方式非常的清晰。

我们可以看一个spring的类,采用的也是这种设计方式。

接口隔离Spring实例1

将功能分类拆成一个一个的“瘦”接口,然后再通过继承将接口整合。类似于上面说的两栖动物。

Spring Data JPA以及tk.mapper也采用了这种设计方式,如下

接口隔离Spring实例2

接口隔离Spring实例3

5、接口隔离原则(Interface Segregation Principle, ISP)

http://jaune162.blog/design-pattern/design-principle/isp.html

作者

大扑棱蛾子(jaune162@126.com)

发布于

2024-02-03

更新于

2024-09-11

许可协议

评论