简介
单一职责原则倾向于设计视角,接口分离原则倾向于实现视角,二者看起来非常相似,但是在某些方面还是有所区别的。
接口分离原则(Interface Segregation Principle):使用方不应该依赖于它不使用的方法(no client should be forced to depend on methods it does not use.)。
ISP 原则是用来处理胖接口或胖基类的,接口或类中包含了茫茫多的方法就称为胖接口或胖基类(简称小胖吧)。使用方在使用小胖的时候,会发现自己没有必要实现某个方法,但由于在小胖中存在,自己不得不实现一个,要么是空方法,要么抛出异常以表明自己不支持。
这时候就需要 ISP 原则出场了,它指导你将接口划分成更小的粒度,使用方只需要实现自己需要的接口即可,而不用继承小胖导致不得不实现小胖交代下来的任务。
在设计原则只里氏替换原则中,我们举的例子就违反了接口分离原则,这里再举一个例子说明这个原则。
需求要求我们做一个二手书设计,要求我们记录书的基本信息,收购的基本信息,以及二手书的鉴定信息等信息。
设计的接口如下:
public interface Book {
public String isbn();
public String author();
public Date publishDate();
public PublisherInfo publisher();
public PurchaseInfo purchaseInfo();
public IdentificationInfo identificationInfo();
}
接口工作良好,很好的支持了网站的运行。但由于业务的变化,网站现在不仅仅要卖二手书了,还要卖新书。这时只是缺少了收购信息和鉴定信息,但是新书本质上还是书,因此我们直接实现了 Book
接口来卖新书。
public abstract class NewBook implements Book {
public PurchaseInfo purchaseInfo() {
return null;
}
public IdentificationInfo identificationInfo() {
return null;
}
}
所有的新书都使用 NewBook
接口,改动也很小就支持了新书的销售,很美好。
这个设计就违反了 ISP 原则,Book
强制所有的书都必须有收购信息和鉴定信息,但新书却并没有这两项,将新书实现 Book
接口强制新书也必须要有这两项信息,无奈只能使用折中办法返回null。
要改变这种情况,我们需要将收购信息和鉴定信息单独拆到一个接口中,二手书的实现继承这个接口,而新书的实现不继承这两个接口。
public interface Book {
public String isbn();
public String author();
public Date publishDate();
public PublisherInfo publisher();
public PurchaseInfo purchaseInfo();
public IdentificationInfo identificationInfo();
}
public interface SecondHand {
public PurchaseInfo purchaseInfo();
public IdentificationInfo identificationInfo();
}
接口拆分成这样已经满足网站的要求了,如果后面网站发展越来越大,鉴定成本不可承受时,有些书籍不作鉴定直接入库,这时我们就需要将 SecondHand
接口再拆分成两个接口,将收购信息和鉴定信息分离开来,不作耦和。
SRP 原则说的是一个类只能有一个改变的理由,ISP 原则指的是使用方不应该依赖它不使用的方法。有的设计符合 SRP 原则却并不符合 ISP 原则。
举一个例子,正常的 Stack
都有 push
pop
方法,如果使用方有一个使用场景,只能使用 push
, 不能使用 pop
, 那么使用方就不能继承 Stack
来实现自己的功能,与 ISP 原则相悖。但是原始的 Stack
设计是完全符合 SRP 原则的,push
与 pop
就是它自己的职责。
从这个例子可以看出,ISP 原则不仅仅能指导我们分离接口,还能帮助判断一个类的继承是不是合理的。
可能有的人觉得这个例子牵强,谁会限制一个 Stack
不能有 pop
方法。大家可以去看下Java 中的 Stack
实现,它继承了Vector
,而 Vector
是一个 List
, Stack
应该只能压入弹出的,但是却继承了 List
的 add
,remove
,get
等方法,是一个很糟糕的实现设计。
接口分离原则与单一职责原则挺相近,但在某些点上是有区别的。日常编码实现某个接口、继承某个类时,问问自己,这样符合 ISP 原则么?