设计模式六大原则(四)----接口隔离原则

一. 接口隔离原则的定义

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.

类间的依赖关系应建立在最小的接口上。

也就是说: 接口尽量细化,接口中的方法尽量少

二. 接口隔离原则和单一职责原则

从功能上来看,接口隔离原则和单一职责原则都是为了提高类的内聚, 降低类之间的耦合, 体现了封装的思想。但二者还是有区别的。

(1)从原则约束来看: 接口隔离原则更关注的是接口依赖程度的隔离;而单一职责原则更加注重的是接口职责的划分。

(2)从接口的细化程度来看: 单一职责原则对接口的划分更加精细,而接口隔离原则注重的是相同功能的接口的隔离。接口隔离里面的最小接口有时可以是多个单一职责的公共接口。

(3)单一职责原则更加偏向对业务的约束: 接口隔离原则更加偏向设计架构的约束。这个应该好理解,职责是根据业务功能来划分的,所以单一原则更加偏向业务;而接口隔离更多是为了“高内聚”,偏向架构的设计。

三. 接口隔离原则的优点

接口隔离原则是为了约束接口、降低类对接口的依赖性,遵循接口隔离原则有以下 5 个优点。

  1. 将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
  2. 接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性。
  3. 如果接口的粒度大小定义合理,能够保证系统的稳定性;然而,如果定义过小,则会造成接口数量过多,使设计复杂化;如果定义太大,灵活性降低,无法提供定制服务,给整体项目带来无法预料的风险。
  4. 使用多个专门的接口能够体现对象的层次,因为可以通过接口的继承,实现对总接口的定义。
  5. 能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。

四. 接口隔离原则的实现方法

在具体应用接口隔离原则时,应该根据以下几个规则来衡量。
1)接口要尽量小
不能出现Fat Interface;但是要有限度,首先不能违反单一职责原则(不能一个接口对应半个职责)。

2)接口要高内聚
在接口中尽量少公布public方法。
接口是对外的承诺,承诺越少对系统的开发越有利。

3)定制服务
只提供访问者需要的方法。例如,为管理员提供IComplexSearcher接口,为公网提供ISimpleSearcher接口。

4)接口的设计是有限度的
了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同, 需要深入了解业务逻辑。

五. 接口隔离原则的建议

  1. 一个接口只服务于一个子模块或业务逻辑;
  2. 通过业务逻辑压缩接口中的public方法;
  3. 已被污染了的接口,尽量去修改;若变更的风险较大,则采用适配器模式转化处理;
  4. 拒绝盲从

五. 案例分析

下面以学生成绩管理为例来说明接口隔离原则:

分析:学生成绩管理程序一般包含查询成绩、新增成绩、删除成绩、修改成绩、计算总分、计算平均分、打印成绩信息等功能,通常我们会怎么做呢?

一: 最初的设计

通常我们设计接口的方式如下:

public interface IStudentScore {
    // 查询成绩
    public void queryScore();

    // 修改成绩
    public void updateScore();

    // 添加成绩
    public void saveScore();

    // 删除成绩
    public void delete();

    // 计算总分
    public double sum();

    // 计算平均分
    public double avg();

    // 打印成绩单
    public void printScore();

}

我们会吧所有的功能都放在一个接口里面. 这会产生什么样的问题呢?
首先, 接口的方法很多, 不利于扩展. 比如: 学生只有查看成绩,打印成绩单的权限, 没有增删改的权限; 老师拥有所有的权限.
查询成绩单:

package com.lxl.www.designPatterns.sixPrinciple.interfaceSegregationPrinciple.score;

public class QueryScore implements IStudentScore{
    @Override
    public void queryScore() {
        // 查询成绩
    }

    @Override
    public void updateScore() {
         // 没有权限
    }

    @Override
    public void saveScore() {
        // 没有权限
    }

    @Override
    public void delete() {
        // 没有权限
    }

    @Override
    public double sum() {
        // 没有权限
        return 0;
    }

    @Override
    public double avg() {
        // 没有权限
        return 0;
    }

    @Override
    public void printScore() {
        //打印成绩单
    }
}

操作成绩单

package com.lxl.www.designPatterns.sixPrinciple.interfaceSegregationPrinciple.score;

public class Operate implements IStudentScore{
    @Override
    public void queryScore() {

    }

    @Override
    public void updateScore() {

    }

    @Override
    public void saveScore() {

    }

    @Override
    public void delete() {

    }

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

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

    @Override
    public void printScore() {

    }
}

可以看出问题. 查询成绩单, 我们只会用到两个方法, 可是因为实现了接口, 不得不重写所有的方法.
如果这时候增加需求--发送给家长, 只有老师才有这个权限, 学生没有这个权限. 可是, 在接口中增加一个抽象方法以后, 所有的实现类都要重写这个方法. 这就违背了开闭原则.

2. 使用接口隔离原则的设计

采用接口隔离原则设计的接口, UML图如下:

public interface IQueryScore {
    // 查询成绩
    public void queryScore();

    // 打印成绩单
    public void printScore();
}

public interface IOperateScore {

    // 修改成绩
    public void updateScore();

    // 添加成绩
    public void saveScore();

    // 删除成绩
    public void delete();

    // 计算总分
    public double sum();

    // 计算平均分
    public double avg();

}


public class StudentOperate implements IQueryScore{
    @Override
    public void queryScore() {
        // 查询成绩
    }

    @Override
    public void printScore() {
        //打印成绩单
    }
}


public class TeacherOperate implements IQueryScore, IOperateScore{
    @Override
    public void queryScore() {

    }

    @Override
    public void updateScore() {

    }

    @Override
    public void saveScore() {

    }

    @Override
    public void delete() {

    }

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

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

    @Override
    public void printScore() {

    }
}

我们将原来的一个接口进行了接口拆分. 分为查询接口和操作接口. 这样学生端就不需要重写和他不相关的接口了.

如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等 3 个模块中,其类图如图 1 所示

#Java工程师面试常考题##学习路径#
全部评论
感谢参与【创作者计划3期·技术干货场】!欢迎更多牛油来写干货,瓜分总计20000元奖励!!技术干货场活动链接:https://www.nowcoder.com/link/czz3jsgh3(参与奖马克杯将于每周五结算,敬请期待~)
点赞 回复 分享
发布于 2021-06-22 16:20
**笔者亲写,要是觉得还不错,记得点赞搜藏哦,后面还有更多精彩文章**
点赞 回复 分享
发布于 2021-07-07 09:52

相关推荐

10-27 17:26
东北大学 Java
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-21 17:16
科大讯飞 算法工程师 28.0k*14.0, 百分之三十是绩效,惯例只发0.9
点赞 评论 收藏
分享
1 5 评论
分享
牛客网
牛客企业服务