zoukankan      html  css  js  c++  java
  • 设计模式:依赖倒置原则

    640?wx_fmt=jpeg

    依赖倒置原则定义

    依赖倒置原则(Dependence Inversion Principle ,DIP)定义如下:

    High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.

    翻译过来为:

    • 高层模块不应该依赖低层模块,两者都应该依赖抽象

    • 抽象不应该依赖细节

    • 细节应该依赖抽象

    也可以说高层模块,低层模块,细节都应该依赖抽象

    每一个逻辑的实现都是由颗粒原子逻辑组成的,颗粒原子逻辑就是低层模块,而颗粒原子逻辑组成的模块就是高层模块。在java语言中,抽象就是接口或抽象类,两都都是不能直接被实例化的,细节就是实现类,实现接口或继承抽象类而产生的类就是细节,两者都可以直接被实例化。

    依赖倒置原则在java语言中,表现是:

    • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。

    • 接口或抽象类不依赖实现类

    • 实现类依赖接口或抽象类

    更加精简的定义就是“面向接口编程”—OOD(Object-Oriented Design,面向对象设计)的精髓之一。

    依赖倒置原则的好处

    采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定,降低并行开发引起的风险,提高代码的可读性和可维护性。

    为什么我们要符合依赖倒置原则

    我们通过一个例子说明依赖倒置原则对于开发有上面的好处。

    大家都喜欢阅读,阅读文学经典滋润自己的内心心灵,下面是小明同学阅读文学经典的一个类图:

    640?wx_fmt=other

    小明阅读文学数据类图

    文学经典的源代码:

    //文学经典类
    public class LiteraryClassic{
        //阅读文学经典
        public void read(){
           System.out.println("文学经典阅读,滋润自己的内心心灵");
        }
    }
    

    小明类:

    //小明类
    public class XiaoMing{
        //阅读文学经典
        public void read(LiteraryClassic literaryClassic){
            literaryClassic.read();
        }
    }
    

    场景类:

    public class Client{
       public static void main(Strings[] args){
          XiaoMing xiaoming = new XiaoMing();
          LiteraryClassic literaryClassic = new LiteraryClassic();
          //小明阅读文学经典
          xiaoming.read(literaryClassic);
       }
    
    }
    

    看,我们的实现,小明同学可以阅读文学经典了。

    小明同学看了一段文学经典后,忽然他想看看看小说来放松一下自己,我们实现一个小说类:

    小说类源代码

    //小说类
    public class Novel{
        //阅读小说
        public void read(){
           System.out.println("阅读小说,放松自己");
        }
    }
    

    现在我们再来看代码,发现XiaoMing类的read方法只与文学经典LiteraryClassic类是强依赖,紧耦合关系,小明同学竟然阅读不了小说类。这与现实明显的是不符合的,代码设计的是有问题的。那么问题在那里呢?

    我们看小明类,此类是一个高层模块,并且是一个细节实现类,此类依赖的是一个文学经典LiteraryClassic类,而文学经典LiteraryClassic类也是一个细节实现类。这是不是就与我们说的依赖倒置原则相违背呢?依赖倒置原则是说我们的高层模块,实现类,细节类都应该是依赖与抽象,依赖与接口和抽象类。

    为了解决小明同学阅读小说的问题,我们根据依赖倒置原则先抽象一个阅读者接口,下面是完整的uml类图:

    640?wx_fmt=other

    读者与阅读动作解耦

    IReader接口:

    public interface IReader{
       //阅读
       public void read(IRead read){
           read.read();
       }
    
    }
    

    再定义一个被阅读的接口IRead:

    public interface IRead{
       //被阅读
       public void read();
    }
    

    再定义文学经典类和小说类:

    //文学经典类
    public class LiteraryClassic implements IRead{
        //阅读文学经典
        public void read(){
           System.out.println("文学经典阅读,滋润自己的内心心灵");
        }
    }
    

    小说类:

    //小说类
    public class Novel implements IRead{
        //阅读小说
        public void read(){
           System.out.println("阅读小说,放松自己");
        }
    }
    

    再实现小明类:

    //小明类
    public class XiaoMing implements IReader{
        //阅读
        public void read(IRead read){
            read.read();
        }
    }
    

    然后,我们再让小明分别阅读文学经典和小说:

    Client:

    public class Client{
       public static void main(Strings[] args){
          XiaoMing xiaoming = new XiaoMing();
          IRead literaryClassic = new LiteraryClassic();
          //小明阅读文学经典
          xiaoming.read(literaryClassic);
    
          IRead novel = new Novel();
          //小明阅读小说
          xiaoming.read(novel);
       }
    
    }
    

    至此,小明同学是可以阅读文学经典,又可以阅读小说了,目的达到了。

    为什么依赖抽象的接口可以适应变化的需求?这就要从接口的本质来说,接口就是把一些公司的方法和属性声明,然后具体的业务逻辑是可以在实现接口的具体类中实现的。所以我们当依赖对象是接口时,就可以适应所有的实现此接口的具体类变化。

    依赖的三种方法

    依赖是可以传递,A对象依赖B对象,B又依赖C,C又依赖D,……,依赖不止。只要做到抽象依赖,即使是多层的依赖传递也无所谓惧。

    构造函数传递依赖对象

    在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入:

    构造函数注入:

    //小明类
    public class XiaoMing implements IReader{
         private IRead read;
         //构造函数注入
         public XiaoMing(IRead read){
            this.read = read;
         }
    
        //阅读
        public void read(){
            read.read();
        }
    }
    

    Setter方法传递依赖对象

    在类中通过Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入:

    //小明类
    public class XiaoMing implements IReader{
         private IRead read;
         //Setter依赖注入
         public setRead(IRead read){
            this.read = read;
         }
    
        //阅读
        public void read(){
            read.read();
        }
    }
    

    接口声明依赖

    在接口的方法中声明依赖对象,在为什么我们要符合依赖倒置原则的例子中,我们采用了接口声明依赖的方式,该方法也叫做接口注入。

    依赖倒置原则的经验

    依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。我们在项目中使用这个原则要遵循下面的规则:

    • 每个类尽量都有接口或者抽象类,或者抽象类和接口两都具备

    • 变量的表面类型尽量是接口或者抽象类

    • 任何类都不应该从具体类派生

    • 尽量不要覆写基类的方法

    • 结合里氏替换原则使用

    里氏替换原则:父类出现的地方子类就能出现。结合本章我们得出了一个通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系。抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

    依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要方法,在项目中,大家只要记住是”面向接口编程”就基本上是抓住了依赖倒置原则的核心了。

  • 相关阅读:
    Codechef EDGEST 树套树 树状数组 线段树 LCA 卡常
    BZOJ4319 cerc2008 Suffix reconstruction 字符串 SA
    Codechef STMINCUT S-T Mincut (CodeChef May Challenge 2018) kruskal
    Codeforces 316G3 Good Substrings 字符串 SAM
    Codechef CHSIGN Change the Signs(May Challenge 2018) 动态规划
    BZOJ1396 识别子串 字符串 SAM 线段树
    CodeForces 516C Drazil and Park 线段树
    CodeForces 516B Drazil and Tiles 其他
    CodeForces 516A Drazil and Factorial 动态规划
    SPOJ LCS2
  • 原文地址:https://www.cnblogs.com/hgmyz/p/12350904.html
Copyright © 2011-2022 走看看