接口隔离原则ISP(Interface Segregation Principle)
Clientsshould not be forced to depend upon interfaces that they do not use.
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
它包含了2层意思:
- 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。但细化拆分并不是无限制的细化每一个方法就对应写一个接口,细化拆分的下限和底线是做到满足单一职责。
- 接口的依赖(继承)原则:如果一个接口a依赖(继承)另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应该遵循上述原则:不应该包含用户不使用的方法。
反之,则说明接口a被b给污染了,应该重新设计它们的关系。
如果用户被迫依赖他们不使用的接口,当接口发生改变时,他们也不得不跟着改变。换而言之,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。
例01
现在设计一个接口描述人类的一些特性,比如人会吃饭、睡觉、步行、讲话、工作、思考等等,一个真实的人会具备所有的这些特性,而机器人只具备其中的某些特性,类图设计如下:
可以看到我们在Human接口中的方法描述了人的基本特性,真实的人Man类实现Human接口并实现接口的全部方法,其它的假人Dummy、扫地机器人SweepRobot、双足机器人BipedRobot以及AI机器人AIRobot它们也实现了Human接口,机器人只具备某一些人类的特性,比如扫地机器人它只会扫地别的啥也不会干,AI机器人可能会讲笑话唱歌与人进行简短的对话,双足机器人会像人类一样直立行走,假人则只具备人类的形状,但是由于它们都实现了Human接口,所以Humman中的所有方法即便它们不需要也不得不去实现。这个就是违反接口隔离原则的例子,用代码来实现该设计,你会更容易发现问题:
public interface Human {
public void talk();
public void sleep();
public void walk();
public void eat();
public void work();
public void think();
public void display();
}
/** 真实的人 */
public class Man implements Human {
@Override
public void talk() {
System.out.println("讲话");
}
@Override
public void sleep() {
System.out.println("睡觉");
}
@Override
public void walk() {
System.out.println("行走");
}
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void work() {
System.out.println("工作");
}
@Override
public void think() {
System.out.println("思考");
}
@Override
public void display() {
System.out.println("拥有人类外形");
}
}
/** 假人 */
public class Dummy implements Human {
@Override
public void display() {
System.out.println("拥有人类外形");
}
@Override
public void talk() {}
@Override
public void sleep() {}
@Override
public void walk() {}
@Override
public void eat() {}
@Override
public void work() {}
@Override
public void think() {}
}
/** 双足机器人 */
public class BipedRobot implements Human {
@Override
public void display() {
System.out.println("拥有人类外形");
}
@Override
public void walk() {
System.out.println("双足直立行走");
}
@Override
public void talk() {}
@Override
public void sleep() {}
@Override
public void eat() {}
@Override
public void work() {}
@Override
public void think() {}
}
/** AI机器人 */
public class AIRobot implements Human {
@Override
public void talk() {
System.out.println("我能讲笑话、还能聊天哦");
}
@Override
public void think() {
System.out.println("我还能回答问题、下围棋");
}
@Override
public void display() {
System.out.println("我是虚拟程序,没有外形");
}
@Override
public void walk() {}
@Override
public void sleep() {}
@Override
public void eat() {}
@Override
public void work() {}
}
以上就是实现代码,你会发现Dummy、SweepRobot、BipedRobot和AIRobot几个类只实现了Human接口的几个方法,其它方法都做了空实现,只有Man类实现了全部方法,也就是说对于机器人相关的实现类而言,接口方法是存在冗余的,所以按照接口隔离原则我们这种情况下就要对接口进行细化拆分,修改类图如下:
可以看到我们将接口进行了拆分,相关方法独立成为接口,每一个实现类只依赖跟它直接相关的接口。相关代码修改如下:
public interface Intelligence {
public void talk();
public void think();
}
public interface Work {
public void work();
}
public interface Display {
public void display();
}
public interface Action {
public void walk();
}
public interface Human extends Intelligence, Work, Display, Action {
public void sleep();
public void eat();
/** 真实的人 */
public class Man implements Human {
@Override
public void talk() {
System.out.println("讲话");
}
@Override
public void sleep() {
System.out.println("睡觉");
}
@Override
public void walk() {
System.out.println("行走");
}
@Override
public void eat() {
System.out.println("吃饭");
}
@Override
public void work() {
System.out.println("工作");
}
@Override
public void think() {
System.out.println("思考");
}
@Override
public void display() {
System.out.println("拥有人类外形");
}
}
/** 假人 */
public class Dummy implements Display {
@Override
public void display() {
System.out.println("拥有人类外形");
}
}
/** 双足机器人 */
public class BipedRobot implements Display, Action {
@Override
public void display() {
System.out.println("拥有人类外形");
}
@Override
public void walk() {
System.out.println("双足直立行走");
}
}
/** 扫地机器人 */
public class SweepRobot implements Display, Work {
@Override
public void display() {
System.out.println("我是圆形的");
}
@Override
public void work() {
System.out.println("打扫卫生");
}
}
/** AI机器人 */
public class AIRobot implements Display, Intelligence {
@Override
public void talk() {
System.out.println("我能讲笑话、还能聊天哦");
}
@Override
public void think() {
System.out.println("我还能回答问题、下围棋");
}
@Override
public void display() {
System.out.println("我是虚拟程序,没有外形");
}
}
现在机器人相关的实现类代码变的简洁了许多,再也不用实现跟它没有关系的方法做空实现了。
接口隔离原则体现的OO特征:封装、抽象。
接口隔离原则可以给系统带来灵活性的优点,但是接口不能无限制的细分,细化拆分必须在满足单一职责的条件下进行,这个细化的颗粒度的把握往往要凭经验常识或者根据业务逻辑来判断。接口隔离原则要求我们的接口或者类尽量使用原子接口和原子类来组装,一个接口只服务于一个模块或者业务逻辑。