面向对象的六大原则(五)

接口隔离原则的定义

依赖倒置原则的英文为Interface Segregation Principle,简称就是ISP。定义为:
Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依赖它不需要的接口。)

在这里,clients(客户端)指的是一个接口的实现类。通俗来说就是:建立简洁单一的接口,不要建立提供一些用不着方法的臃肿接口。这种接口,使得实现类被强制地完全不必要地针对那些用不着的方法提供了假的(dummy)或者空的(empty)实现;除此之外,实现类还受制于接口方法的改变。依赖的接口发生一个方法的增加或者一个方法的签名变了都会使得所有的实现类都改变了,即使这些实现类完全用不上这些方法。因此接口隔离原则提倡将一个臃肿的接口分离成更小的、高内聚的接口。

接口隔离原则的示例

违背了接口原则的示例

先来看一个反例,我们的应用程序需要创建各种各样的玩具,每个玩具都有自己的价格和颜色。一些玩具像玩具汽车或者玩具火车能移动,还有一些玩具像玩具飞机不仅能移动,而且还可以飞。
ShowImage

我们定义出玩具的接口,代码如下

1
2
3
4
5
6
7
8
9
public interface Toy {
void setPrice(double price);
void setColor(String color);
void move();
void fly();
}

好,这样我们的玩具飞机ToyPlane类实现这个Toy接口就可以完成这个功能了。然而问题也来了,如果我们要创建出一个玩具房子,玩具房子不能移动也不能飞,也要实现move()方法和fly()方法,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ToyHouse implements Toy {
double price;
String color;
@Override
public void setPrice(double price) {
this.price = price;
}
@Override
public void setColor(String color) {
this.color = color;
}
@Override
public void move() {
}
@Override
public void fly() {
}
}

move()方法和fly()对我们的ToyHouse实现类来说完全是不必要的,使得ToyHouse实现类代码的可读性也变低了这样的接口有点臃肿了。

违背了接口隔离原则也违背了开闭原则。假如说,现在Toy接口为了适应创建新的玩具机器人增加了walk()方法,这样一来,所有现有的实现了Toy接口的实现类都必须要修改增加walk()方法的实现,很显然,违背了开闭原则,而且这样的结果也不是我们想要得到的。

依照接口隔离原则修改的接口

那么现在怎么办呢,我们把Toy接口分解一下,成为Toy接口,Movable接口以及Flyable接口,类图如下
ShowImage

新的Toy接口代码

1
2
3
4
5
public interface Toy {
void setPrice(double price);
void setColor(String color);
}

Movable接口

1
2
3
public interface Movable {
void move();
}

Flyable接口

1
2
3
public interface Flyable {
void fly();
}

实现类ToyHouse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ToyHouse implements Toy {
double price;
String color;
public void setPrice(double price) {
this.price = price;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "ToyHouse: Toy house - Price: " + price + " Color: " + color;
}
}

实现类ToyCar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ToyCar implements Toy, Movable {
double price;
String color;
public void setPrice(double price) {
this.price = price;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("ToyCar: Start moving car.");
}
@Override
public String toString() {
return "ToyCar: Movable Toy car - Price: " + price + " Color: " + color;
}
}

实现类ToyPlane

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ToyPlane implements Toy, Movable, Flyable {
double price;
String color;
public void setPrice(double price) {
this.price = price;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("ToyPlane: Start moving plane.");
}
public void fly() {
System.out.println("ToyPlane: Start flying plane.");
}
@Override
public String toString() {
return "ToyPlane: Movable and flyable toy plane - Price: " + price + " Color: " + color;
}
}

现在的实现类只需要实现它们所感兴趣的接口就可以了,我们的类有更少的代码冗余,代码具备更好的可读性,并且在接口方法变化时,实现类的修改也更少了。

接口隔离原则的总结

采用接口隔离原则时,应当做到以下几点:

  • 接口要尽量小,但是也是要有限度,不要过度设计,也不能违背了单一职责原则。
  • 接口应该高内聚,提高接口、类、模块的处理能力,减少对外的交互。
  • 提供定制服务,给调用者提高它需要的方法,将它不需要的方法隐藏起来。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!