设计模式之六大设计原则之《三》高效的依赖倒置原则

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013821237/article/details/84034337

参考书籍:设计模式之禅--秦小波

上篇回顾:上期讲到了里氏替换原则(LSP),讲的不好还请见谅。上篇文末留下了一个问题:“正方形是长方形吗?”。这是一个很经典的LSP问题,我们知道,从几何学角度来看,正方形是特殊的长方形,特殊在长和宽长度相等。从这个角度看,长方形的范围更宽泛:既可以长宽不等,又可以长宽相等,可以认为是包含和被包含的关系。那么在编程思想里,可以认为正方形是长方形吗?我们看下如下长方形的代码:

class Rectangle{
    private int length;
    private int width;
    
    public void setLength(int length)
        {
            this.length=length;
        }
    public int getLength()
        {
            return this.length;
        }
        
    ......

}

这个类只有两个简单的属性:长和宽。下面我们再看下正方形的代码:

class Square{
    private int length;
    private int width;
    
    public void setLength(int length)
        {
            this.length=length;
            this.width=length;//这里要敲下黑板
        }
    public int getLength()
        {
            return this.length;
        }
        
    ......

}

我们发现,由于正方形有长宽相等的限制,所以当我们设置了正方形的长时,宽也需要对应着改变。因而正方形无法完全替代长方形,替换后改变了原有的长方形的性质,因此 从这方面来看,“正方形不是长方形”。那怎么办?从小到大我都觉得他们有联系呀!我们可以这样看待他们的关系:他们都是四边形,却达不到替换的程度。


依赖倒置原则

定义:依赖倒置原则(DIP),全称Dependence Inversion Principle,这是什么意思呢?先看下原始定义:High level modules should not depend upon low level modules.Both should depend uponabstractions.Abstractions should not depend upon details.Details should depend upon abstractions.简单来说,即是:具体类之间的关系应该依靠接口实现,但接口的设计不能依据具体的某个类。换言之也就是:面向接口编程!

介绍了该原则大概是啥,下面我们具体分析下依赖倒置原则中“依赖”和“倒置”是什么。

什么是依赖?当我们需要用到一个对象时,就要把这个对象引进来,我中有你,就产生了依赖。常用的依赖方式有:

  1. set的方式赋值进来,然后使用这个对象。
  2. 构造方法引入对象,然后使用这个对象。
  3. 接口声明,具体类实现,然后使用这个对象。

那么,倒置该作何解呢。既然有倒置,就应该有“正置”,以我们吃饭为例,按我们人正常的行事逻辑,当我们需要吃饭,第一步是找到食物(即吃什么),然后拿起筷子(用什么餐具),最后开始食用。这个过程中,我们明确的知道,要吃什么,要用什么餐具,一切都是按照所需去取(引入依赖)所得,这就是“正置”。慢慢地我们发现,食物有很多种,并不是只有香蕉,还有鱼肉,面条,青菜等,用的餐具也不再确定,可能用筷子,用勺子,用叉子...怎么办?作为“正置”流程的开发者,代码的流程和逻辑我都知道,所以,我可以根据需求的改变,去修改代码。

大家有没有发现问题呢?如果这个项目并非我一个人完成的,而是多个人完成的,甚至是使用的第三方SDK,我并不能完全知道业务的完整逻辑,那怎么办呢?这个时候“倒置”就表现出了他的优势,我们先定义三个抽象接口:

interface IFood{//食物接口
    String name;
    ...
}

interface ITableware{//餐具接口
    String name;
    ...
}

interface IEat{//用餐接口

    public void eat(IFood food,ITableware tableware);
    ...
}

 张三负责拓展食物方向的业务,李四负责餐具方面的拓展,王五负责食用方面的逻辑,在IEat的实现类里,IFood可以是任意子类的实现,如 IFood food=new Rice();//Rice实现了IFood接口,IFood food=new Noddle();//Noddle实现了IFood接口。餐具也是相同的逻辑。再也不必担心食物或者餐具的改变去修改代码了,张三李四王五都可以各自完成自己业务,极大减少了各自的牵绊。

在上面的例子里,Rice,Noddle等具体类不再是被主动调用使用,而是先定义了一个接口,具体的实现类根据需要被引入的。也就是产生依赖的Eat类并不知道需要引入哪些具体依赖类,这些最终被依赖的具体类是通过接口的方式引入的,这就是倒置。

建议:依赖倒置的本质就是通过接口或者抽象类使各个模块尽量独立,实现松耦合,降低彼此之间的影响。秦小波给出了如下几点建议:

  1. 每个类尽量有接口或者抽象类,或者抽象类接口兼备。接口是依赖倒置的基础。
  2. 变量的表面类型尽量是接口或者是抽象类。
  3. 任何类都不应该从具体类派生。一层两层可能看不出来问题,但是如果继承了很多层,我相信你会回头反思为什么当初没有设计成接口或者抽象类的。
  4. 尽量不要覆写基类的方法,这样会影响基类的本质,改变基类的本来意愿。
  5. 结合里氏替换原则使用。我们知道里氏替换原则就是讲的父子类关系的,和这里的面向接口编程,有水乳交融之处。

最后,每篇一问:

我们一直说依赖倒置,抽象不能依据具体,这样的说法会在哪些例子里被反驳呢?欢迎留言。

猜你喜欢

转载自blog.csdn.net/u013821237/article/details/84034337
今日推荐