01.设计原则之接口隔离原则

123 阅读7分钟

接口隔离原则

定义

Interface Segregation Principle
指明客户端不应被迫使用对其而言无用的方法或功能。
拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户端将会只需要知道他们感兴趣的方法。
目的是系统解开耦合,从而容易重构,更改和重新部署。

客户端:可以理解为接口的调用者或者使用者 理解接口隔离原则的关键,就是理解接口的含义,在这个原则中,我们可以把接口理解为:

  1. 一组API接口
  2. 单个API接口或者函数
  3. 如JAVA语言中的Interface

当我们把接口理解为一组API时,如果部分接口或者部分功能被客户端使用,那么我们就需要将这部分的接口隔离出来,单独给客户端使用。 当我们把接口理解为单个API或者函数,如果函数中部分功能,能被客户端调用,那么我们就需要单独把这部分功能封装成函数给调用者使用。 当我们把接口理解为JAVA语言中的interface,我们的interface设计尽量要单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

简单来说,作为程序设计师在设计接口时,尽量缩小接口的范围,使得客户端的类不必实现其不需要的行为,不应该强迫客户端程序依赖 它们不用的方法。接口应该具备小而完备的行为。

根据接口隔离原则,你必须将"臃肿"的方法拆分为多个颗粒度更小的具体方法,客户端必须仅实现其实际需要的方法。否则,对于"臃肿"的接口 修改可能会导致程序出错,即使客户端根本没有使用修改后的方法。在JAVA这门面向对象语言中,一个类只能单继承,可以多实现。因此我们 没有必要将大量无关的行为塞到一个接口中,可以进行拆分为更精细的接口。

从定义来看接口隔离原则与单一职责原则的区别

单一职责原则针对的是模块、类、接口的设计。 接口隔离的原则相对于单一职责原则,一方面更侧重于接口的最小化设计,另一方面更侧重于接口使用的精确性 接口隔离原则提供了一种判断接口职责是否单一的标准:通过调用者如何使用接口来间接判定,如果调用者只使用了部分接口或接口的部分功能 那么接口设计的就不够职责单一

实战分析

下面我们以学生成绩管理程序介绍接口隔离原则 【例1】学生成绩管理程序 分析:学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、査询成绩信息等功能, 如果将这些功能全部放到一个接口中显然不太合理, 正确的做法是将它们分别放在输入服务、统计服务和打印服务等 3 个服务中

在输入服务中包含的功能插入成绩、删除成绩、修改成绩;在统计服务中包含的功能计算总分、计算均分;在打印服务包含打印成绩信息、査询成绩信息

示意代码如下:

public interface InputService {
    void insert();
    void remove();
    void modify();
}

public interface ReportService {
    double countTotalScore();
    double countAvg();
}

public interface PrintService {
    String printInfo();
    String queryInfo();
}

public class Client implements InputService,ReportService,PrintService {

    @Override
    public String printInfo() {
        return null;
    }

    @Override
    public double countTotalScore() {
        return 0;
    }

    @Override
    public void insert() {

    }

    @Override
    public void remove() {

    }

    @Override
    public String queryInfo() {
        return null;
    }

    @Override
    public double countAvg() {
        return 0;
    }

    @Override
    public void modify() {

    }
}

【例2】动态表单设计器 分析:动态表单设计器包含功能,依据用户托拉拽表单控件,生成数据库表中的字段,动态生成sql语句和ddl语句,同时适配各种关系型数据库 我们不可能把sql语句和ddl语句都放入到一个服务中。我们会拆分为sql服务和ddl服务。

违例设计:

public interface DatabaseService {

    /***
     * 查询语句
     */
    void select();

    /***
     * 删除语句
     */
    void delete();

    /***
     * 更新语句
     */
    void update();

    /***
     * 新增语句
     */
    void insert();


    /***
     * 创建表
     */
    void create();

    /***
     * 修改表
     */
    void alert();

    /***
     * 新增字段
     */
    void add();

    /***
     * 删除表
     */
    void drop();

}

public class Mysql implements DatabaseService {

    @Override
    public void create() {

    }

    @Override
    public void select() {

    }

    @Override
    public void alert() {

    }

    @Override
    public void delete() {

    }

    @Override
    public void update() {

    }

    @Override
    public void drop() {

    }

    @Override
    public void add() {

    }

    @Override
    public void insert() {

    }
}

public class Oracle implements DatabaseService {

    @Override
    public void create() {

    }

    @Override
    public void select() {

    }

    @Override
    public void alert() {

    }

    @Override
    public void delete() {

    }

    @Override
    public void update() {

    }

    @Override
    public void drop() {

    }

    @Override
    public void add() {

    }

    @Override
    public void insert() {

    }
}

正例设计:

public interface SqlService {

    /***
     * 查询语句
     */
    void select();

    /***
     * 删除语句
     */
    void delete();

    /***
     * 更新语句
     */
    void update();

    /***
     * 新增语句
     */
    void insert();

}

public interface DdlService {

    /***
     * 创建表
     */
    void create();

    /***
     * 修改表
     */
    void alert();

    /***
     * 新增字段
     */
    void add();

    /***
     * 删除表
     */
    void drop();

}

public class Mysql implements SqlService,DdlService {

    @Override
    public void create() {

    }

    @Override
    public void select() {

    }

    @Override
    public void alert() {

    }

    @Override
    public void delete() {

    }

    @Override
    public void update() {

    }

    @Override
    public void drop() {

    }

    @Override
    public void add() {

    }

    @Override
    public void insert() {

    }
}

public class Oracle implements SqlService,DdlService {

    @Override
    public void create() {

    }

    @Override
    public void select() {

    }

    @Override
    public void alert() {

    }

    @Override
    public void delete() {

    }

    @Override
    public void update() {

    }

    @Override
    public void drop() {

    }

    @Override
    public void add() {

    }

    @Override
    public void insert() {

    }
}

【例3】JDK源码 以JDK源码为例,为读者进行更明确的解释。接口隔离原则,主要体现在以下两个方面。

  1. 不要使用没有任何依赖关系的接口 首先我们来看以下代码:
public class JDK {

    public static void main(String[] args) {
        List<Object> list = Collections.emptyList();
        list.add(1);
    }

}

运行结果:

Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.AbstractList.add(AbstractList.java:148)
	at java.util.AbstractList.add(AbstractList.java:108)
	at 接口隔离原则.JDK.main(JDK.java:17)
	
从报错信息,我们知道在调用add方法出了问题,因为Collections.emptyList();
创建了一个空集合,是不能添加元素的,进入源码查看

private static class EmptyList<E>
        extends AbstractList<E>
        implements RandomAccess, Serializable {}
EmptyList集合实现了RandomAccess接口,这个接口是不应该实现的,违背了接口隔离原则

RandomAccess接口是为集合提供快速随机访问的,EmptyList集合中不支持添加对象,怎么会 有对象,没有对象何谈快速随机访问。 RandomAccess作为快速随机访问的标志类,就是一个明确的职责划分,符合单一职责原则; 而接口隔离原则在此处的作用是告诉大家:RandomAccess当用时则用,不当用时勿用。

  1. 一个类对另外一个类的依赖性应当是建立在最小的接口上的 以JDK的ArrayList源码设计进行说明,体会接口隔离原则的意义。我们来看以下ArrayList类结构:
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{}

从上边的类结构代码中能够很明显地看到,ArrayList有四种特质:
1. 集合特质:因为实现了List接口。
2. 快速随机访问:因为实现了RandomAccess接口。
3. 支持克隆:因为实现了Cloneable接口。
4. 支持序列化:因为实现了Serializable接口。
这样的类结构,首先是遵循了单一职责原则,按职责划分为不同的接口;
然后在此基础上,接口隔离原则登场,细化接口的方法,保持接口的纯洁性,在满足需求的前提下,
尽量减少接口的方法,做到专业、精确、最小化接口。

总结

与其他原则一样,你可能会过度使用这条原则。不要进一步 划分已经非常具体的接口。记住,创建的接口越多,代码就 越复杂。因此要保持平衡。一般来说接口中仅包含某业务模块的方法即可,不应该有其他业务模块的方法。