设计模式(Java版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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方法;

■ 尽量修改污染了的接口,若修改的风险较大,则可采用适配器模式进行转化处理;

■ 接口设计应因项目而异,因环境而异,不能教条照搬。

接口隔离原则和其他的设计原则一样,都是需要花费时间和精力来进行设计和筹划的,但是它带来了设计的灵活性,并降低整个项目的风险,当业务变化时能够快速应付。在设计接口时应根据经验和常识决定接口的粒度大小,如果接口粒度太小,导致接口数量剧增,给开发带来难度;如果接口粒度太大,灵活性降低,无法提供定制服务,给项目带来无法预计的风险。