2.4 接口隔离原则
接口隔离原则的英文名称是:Interface Segregation Principle,简称ISP。
2.4.1 接口隔离原则的定义
在讲接口隔离原则之前,需要首先明确“接口”的概念,接口分为两种。
■ 实例接口(Object Interface),在Java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事物所具有的方法特征的描述,也称做一个“接口”,这仅是一种逻辑上的抽象。例如,定义一个Person类,使用Person zhangsan = new Person()产生一个实例,该实例遵从的标准是Person这个类,Person就是zhangsan的接口。
■ 类接口(Class Interface),是指在 Java 中使用 interface 严格定义的接口,例如, java.lang.Runnable就是一个Java线程接口。
针对“接口”这两种不同的含义,接口隔离原则的表达方式以及含义都有所不同,接口隔离原则有如下两种定义。
第一种定义:
Clients should not be forced to depend upon interfaces that they don't use.
意思是:客户端不应该依赖它不需要的接口。
第二种定义:
The dependency of one class to another one should depend on the smallest possible interface。
意思是:类间的依赖关系应该建立在最小的接口上。
接口隔离原则的具体含义如下。
■ 一个类对另外一个类的依赖性应当是建立在最小的接口上的。
■ 一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。因此使用多个专门的接口比使用单一的总接口要好。
■ 不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构,即不要强迫客户使用它们不用的方法,否则这些客户就会面临由于这些不使用的方法的改变所带来的改变。
2.4.2 接口隔离原则的应用
接口隔离原则的应用场合,只提供调用者需要的方法,屏蔽不需要的方法。下述内容用于实现任务描述2.D.4,演示接口隔离原则。
如图2-7所示的电子商务系统,该系统中有订单这个类,并在三个地方会使用到订单类:
■ 一个是门户,只能有查询方法;
■ 一个是外部系统,有添加订单的方法;
■ 一个是管理后台,添加、删除、修改、查询都要用到。
针对这三个不同的应用场景,为满足接口隔离原则,应使用三个不同的接口进行隔离,每个接口中提供的方法不同,这样使每个应用都建立在最小接口上。
图2-7 接口隔离原则类图
IOrderForPortal接口的源代码如下所示。
【描述2.D.4】 IOrderForPortal.java
//用户门户应用接口 public interface IOrderForPortal { public String getOrder(); }
IOrderForOtherSys接口的源代码如下所示。
【描述2.D.4】 IOrderForOtherSys.java
//外部系统应用接口 public interface IOrderForOtherSys { public void insertOrder(); }
IOrderForAdmin接口的源代码如下所示。
【描述2.D.4】 IOrderForAdmin.java
//管理平台应用接口 public interface IOrderForAdmin { public String getOrder(); public void insertOrder(); public void updateOrder(); public void deleteOrder(); }
Order类实现IOrderForPortal、IOrderForOtherSys和IOrderForAdmin 3个接口,其源代码如下所示。
【描述2.D.4】 Order.java
public class Order implements IOrderForAdmin, IOrderForOtherSys, IOrderForPortal { // 返给Portal public static IOrderForPortal getOrderForPortal() { return new Order(); } // 返给OtherSys public static IOrderForOtherSys getOrderForOtherSys() { return new Order(); } // 返给Admin public static IOrderForAdmin getOrderForAdmin() { return new Order(); } public void deleteOrder() { System.out.println("删除订单"); } public String getOrder() { return "返回订单"; } public void insertOrder() { System.out.println("插入订单"); } public void updateOrder() { System.out.println("更新订单"); } }
下面编写一个测试类,测试接口隔离原则,代码如下所示。
【描述2.D.4】 TestISP.java
public class TestISP { public static void main(String[] args) { // 获取用户门户接口对应的实例,该对象只能使用getOrder()方法 IOrderForPortal op = Order.getOrderForPortal(); System.out.println(op.getOrder()); // 获取外部系统接口对应的实例,该对象只能使用insertOrder()方法 IOrderForOtherSys os = Order.getOrderForOtherSys(); os.insertOrder(); // 获取管理平台接口对应的实例,该对象可以使用Order类中提供的所有方法 IOrderForAdmin oa = Order.getOrderForAdmin(); System.out.println(oa.getOrder()); oa.insertOrder(); oa.updateOrder(); oa.deleteOrder(); } }
通过上述代码可以看到,Order 类通过 IOrderForPortal、IOrderForOtherSys 和IOrderForAdmin这3个接口,可以对不同的用户使用不同的接口进行隔离。
接口隔离原则是对接口的定义,同时是对类的定义,应尽量使用原子接口或原子类,其中“原子”在实践应用中可以根据以下几个规则来衡量:
■ 一个接口只对一个子模块或者业务逻辑进行服务;
■ 只保留接口中业务逻辑需要的public方法;
■ 尽量修改污染了的接口,若修改的风险较大,则可采用适配器模式进行转化处理;
■ 接口设计应因项目而异,因环境而异,不能教条照搬。
接口隔离原则和其他的设计原则一样,都是需要花费时间和精力来进行设计和筹划的,但是它带来了设计的灵活性,并降低整个项目的风险,当业务变化时能够快速应付。在设计接口时应根据经验和常识决定接口的粒度大小,如果接口粒度太小,导致接口数量剧增,给开发带来难度;如果接口粒度太大,灵活性降低,无法提供定制服务,给项目带来无法预计的风险。