YAZONG 我的开源

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

 
0 评论0 浏览

1、4接口隔离原则

定义

接口分为两种

实例接口:在java中声明一个类,然后用new关键字产生一个实例,它是对一个类型的事物描述,这是一种接口。比如Person person = new Person()产生了一个实例,Person类就是person的接口,从这个角度看,java中的类也是一种接口。

类接口:interface关键字定义的接口。

隔离两种定义

客户端不应该依赖它不需要的接口(把不需要的接口剔除掉,对接口进行细化,保证其纯洁性)。
类间的依赖关系应该建立在最小的接口上(最小的接口,接口细化,接口纯洁)。
上述两种定义只是一个事物的两种不同描述。

上述两种定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点就是:接口尽量细化,同时接口中的方法尽量少。

案例

案例1-1

图形结构

image.png

代码实例

public class Client1 {
    public static void main(String[] args){
    }
}
interface I{
    void method1();
    void method2();
    void method3();
    void method4();
    void method5();
}
class A{
    public void depend1(I i){
        i.method1();
    }
    public void depend2(I i){
        i.method2();
    }
    public void depend3(I i){
        i.method3();
    }
}
class B implements I{
    public void method1() {
        System.out.println("实现接口I的方法1");
    }

    public void method2() {
        System.out.println("实现接口I的方法2");
    }

    public void method3() {
        System.out.println("实现接口I的方法3");
    }
    //对于类B来说,method4和method5不是必需的,但是由于接口A中有这两个方法,
    //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
    public void method4() {
    }
    public void method5() {
    }
}
class C{
    public void depend1(I i){
        i.method1();
    }
    public void depend4(I i){
        i.method4();
    }
    public void depend5(I i){
        i.method5();
    }
}
class D implements I{
    public void method1() {
        System.out.println("类D实现接口I的方法1");
    }
    //对于类D来说,method2和method3不是必需的,但是由于接口A中有这两个方法,
    //所以在实现过程中即使这两个方法的方法体为空,也要将这两个没有作用的方法进行实现。
    public void method2() {}
    public void method3() {}

    public void method4() {
        System.out.println("类D实现接口I的方法4");
    }
    public void method5() {
        System.out.println("类D实现接口I的方法5");
    }
}

案例1-2(对案例1-1进行接口拆分)

图形结构

image.png

代码示例

public class Client2 {
    public static void main(String[] args) {
    }
}
interface I1 {
    public void method1();
}

interface I2 {
    public void method2();
    public void method3();
}

interface I3 {
    public void method4();
    public void method5();
}

class A{
    public void depend1(I1 i){
        i.method1();
    }
    public void depend2(I2 i){
        i.method2();
    }
    public void depend3(I2 i){
        i.method3();
    }
}

class B implements I1, I2{
    public void method1() {
        System.out.println("类B实现接口I1的方法1");
    }
    public void method2() {
        System.out.println("类B实现接口I2的方法2");
    }
    public void method3() {
        System.out.println("类B实现接口I2的方法3");
    }
}

class C{
    public void depend1(I1 i){
        i.method1();
    }
    public void depend4(I3 i){
        i.method4();
    }
    public void depend5(I3 i){
        i.method5();
    }
}

class D implements I1, I3{
    public void method1() {
        System.out.println("类D实现接口I1的方法1");
    }
    public void method4() {
        System.out.println("类D实现接口I3的方法4");
    }
    public void method5() {
        System.out.println("类D实现接口I3的方法5");
    }
}

案例2-1

图形结构

image.png

代码示例

public class Client3 {
}
abstract class AbstractSearch {
    protected IPettyGirl iPettyGirl;

    public AbstractSearch(IPettyGirl iPettyGirl) {
        this.iPettyGirl = iPettyGirl;
    }
    //搜索美女,列出美女信息
    public abstract void show();
}
interface IPettyGirl{
    //要有较好的面孔
    void goodLooking();
    //要有好身材
    void niceFigure();
    //要有气质
    void greatTemperament();
}
class PettyGirl implements IPettyGirl{

    private String name;
    //美女都有名字
    public PettyGirl(String name) {
        this.name = name;
    }

    public void goodLooking() {
        System.out.println(this.name + "--脸蛋很漂亮.");
    }

    public void niceFigure() {
        System.out.println(this.name + "--气质非常好.");
    }

    public void greatTemperament() {
        System.out.println(this.name + "--身材非常棒.");
    }
}
class Searcher extends AbstractSearch{
    public Searcher(IPettyGirl iPettyGirl) {
        super(iPettyGirl);
    }

    public void show() {
        super.iPettyGirl.goodLooking();
        super.iPettyGirl.greatTemperament();
        super.iPettyGirl.niceFigure();
    }
}

案例2-2

图形结构

image.png

代码示例

public class Client4 {
}

abstract class AbstractSearch {
    protected IGreatTemperamentGirl iGreatTemperamentGirl;
    protected IGoodBodyGirl iGoodBodyGirl;

    public AbstractSearch(IGreatTemperamentGirl iGreatTemperamentGirl) {
        this.iGreatTemperamentGirl = iGreatTemperamentGirl;
    }
    public AbstractSearch(IGoodBodyGirl iGoodBodyGirl) {
        this.iGoodBodyGirl = iGoodBodyGirl;
    }
    abstract void show();
}
interface IGreatTemperamentGirl{
    //要有气质
    void greatTemperament();
}
interface IGoodBodyGirl{
    //要有较好的面孔
    void goodLooking();
    //要有好身材
    void niceFigure();
}

class PettyGirl implements IGreatTemperamentGirl,IGoodBodyGirl {

    private String name;
    //美女都有名字
    public PettyGirl(String name) {
        this.name = name;
    }

    public void goodLooking() {
        System.out.println(this.name + "--脸蛋很漂亮.");
    }

    public void niceFigure() {
        System.out.println(this.name + "--气质非常好.");
    }

    public void greatTemperament() {
        System.out.println(this.name + "--身材非常棒.");
    }
}

class Searcher extends AbstractSearch {

    public Searcher(IGreatTemperamentGirl iGreatTemperamentGirl) {
        super(iGreatTemperamentGirl);
    }

    public Searcher(IGoodBodyGirl iGoodBodyGirl) {
        super(iGoodBodyGirl);
    }

    @Override
    void show() {
        super.iGreatTemperamentGirl.greatTemperament();
        super.iGoodBodyGirl.goodLooking();
        super.iGoodBodyGirl.niceFigure();
    }
}

接口隔离原则和单一职责的区别

说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然,接口隔离原则与单一职责的审视角度是不相同的。其一,单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节,这是业务逻辑上的划分;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。

例如一个接口的职责可能包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,在系统外通过文档约束“不使用的方法不要访问”,按照单一职责原则是允许的,按照接口隔离原则是不允许的,因为它要求“尽量使用多个专门的接口”。专门的接口指什么?就是指提供给每个模块的都应该是单一接口,提供给几个模块就应该有几个接口,而不是建立一个庞大的臃肿的接口,容纳所有的客户端访问。

保证接口的纯洁性(接口约束)

案例

image.png

看到这个案例,想一想,是不是需要把IConnectionManager接口拆封成两个,一个接口是负责连接,一个接口是负责挂电话?如果拆分了,那就不符合单一职责原则了,因为从业务逻辑上来讲,通信的建立和关闭已经是最小的业务单位了,再细分下去就是对业务或是协议(其他业务逻辑)的拆分了。想想看,一个电话要关心3G协议,要考虑中继服务器,等等,这个电话还怎么设计得出来呢?从业务层次来看,这样的设计就是一个失败的设计。一个原则要拆,一个原则又不要拆,那怎么办呢?那么,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。

接口要尽量小

一个原则要拆,一个原则又不要拆,此时,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。

接口要高内聚

什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。不讲任何条件、立刻完成任务的行为就是高内聚的表现。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。

定制服务

一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口(并不一定是java中定义的interface,也可能是一个类或单纯的数据交换),我们设计时就需要为各个访问者(即客户端)定制服务,什么是定制服务?定制服务就是单独为一个个体提供优良的服务,我们在做系统设计时也需要考虑对系统之间或模块之间的接口采用定制服务。采用定制服务就必然有一个要求:只提供访问者需要的方法。

接口设计是有限度的

接口的设计粒度越小,系统越灵活,这是不争的实时。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度,这个“度”如何来判断呢?根据经验和常识判断,没有一个固话或可测量的标准。

抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展,只要抽象这根基线在,细节就脱离不了这个圈圈,始终让你的对象做到”言必行,行必果”。

总结

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。在实践中的原子划分衡量规则:

一个接口只服务于一个子模块或业务逻辑;
通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;
已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,别看到大师是这样做的你就照抄。千万别,环境不同,接口拆分的标准就不同。所以一定要深入了解业务逻辑!

接口是设计时对外部设定的”契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展,只要抽象这跟基线在,细节就脱离不了这个圈圈,始终让你的对象做到”言必行,行必果”。

怎么准确地实践接口隔离原则?实践、经验和领悟!


标题:设计模式-六大设计原则(四)-接口隔离原则
作者:yazong
地址:https://blog.llyweb.com/articles/2020/03/19/1584627088531.html