依赖倒置原则(设计模式原则)

(Dependence Inversion Principle)定义: 高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。//“抽象”指“接口或抽象类”,“细节”指“实现类”

在语言中表现:

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

”A依赖B“指 什么?依赖传递的3种方式?

问题(不好的情况):

概述: 再 “依赖” 关系中,程序要依赖于抽象接口,不要依赖于具体实现。这样降低了客户与实现模块间的耦合。

谁依赖谁要抽象的合理。

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

3  举例说明: 司机驾驶奔驰如3-1图:

依赖倒置原则(设计模式原则)_第1张图片

3-1  司机源码

public class Driver{
   public void drive(Benz benz){
       benz.run();
   }
}
3-2 奔驰车源代码

public class Bez{
    public void run(){
        System.out.println("奔驰开始运行...")   
    }
}

3-3 场景类源代码

public class Client{
    public static void main(Sting[] args){
        Driver zhangsan = new Driver();
        
        Benz benz = new Benz();

        zhangsan.drive(benz);
    }
}

以上代码也算是完成了任务或者基本需求,现在又来新需求了,张三不仅要开奔驰车,还要开宝马车,又该怎么办呢?

3-4  宝马车源代码

public class BMW{
    public void run(){
         System.out.println("宝马车开始运行....");
    }
}


问题来了,宝马车也产生了,张三没法开?因为张三没有开宝马车的方法!(除非要改driver类的方法),一个有驾照的司机竟然只能开奔驰车,而不能开宝马车(要开宝马车改动driver类代码),这也不合理,在现实世界也不合理。 问题关键点在什么地方?就是司机类和奔驰车类之间是一个紧耦合关系,这里新增加一个车类就要修改司机类,这是不稳定性!被依赖者的变更或增加(新增宝马车类)竟然要依赖者(司机类)来承担修改的成本,不好呀!!!

 这样的代码类关系还会引起并行开发的风险; 什么是并行开发风险? 并行开发最大的风险就是风险扩散,本来只是一段程序的错误或异常,逐步波及一个功能,一个模块,甚至最后毁坏整个项目! 为什么风险可能如此大呢? 一个团队,20人开发,个人负责不同的功能模块,甲负责汽车类,乙负责司机类, 在甲没有完成的情况,乙是不能完全编写代码的,缺少汽车类,编译器根本不会让你通过,更不要说单元测试了! 在上面那种不使用依赖倒置原则环境中,所有的开发工作都是单线程的,甲做完,乙再做,然后是丙....,   在小项目中是可以的一个人完成所有的代码开发工作,但是在现在的大中型项目中已经是完全不能胜任了;要协作就要并行开发,要并行开发就要解决模块之间的项目依赖关系,依赖倒置原则就可以做到!!!

引入了依赖倒置原则后的类如图3-2

依赖倒置原则(设计模式原则)_第2张图片

建立两个接口:IDriver 和 ICar 

3-5 司机接口

public interface IDriver{
   public void drive(ICar car);
}

3-6  司机类的实现

public class Driver implements IDriver{
     public void drive(ICar car){
            car.run();
     }
}

3-7  汽车类及两个实现类

public interface ICar{
    public void run();
}

public class Benz implements  ICar{
     public void run(){
           System.out.println("奔驰骑车开始运行....");
     }
}

public class BMW implements ICar{
     public void run(){
         System.out.println("宝马骑车开始运行....");
     }
}
3-8 业务场景代码

public class Client{
    public static  void main(String[] args){
          IDriver zhangsan = new Driver();
         ICar benz = new Benz();

         zhangsan.drive(benz);
    }
}


Client 属于高层业务逻辑,它对底层模块的依赖(关联)都建立在抽象上,zhangsan 的显示类型是IDriver, benz的显示类型是ICar, 也许你要问,在这个高层模块中也调用了底层模块,比如 new Driver()  和 new Benz()等,怎么解释? 确实如此,zhangsan的显示类型是IDriver,是一个接口,是抽象的,非实体化的,在后续的操作中zhangsan 都是以IDriver类型进行操作,屏蔽了细节对抽象的影响。当然,张三如果要开宝马,很容易,只要修改业务场景类就可以了。如下:

3-9 张三驾驶宝马车过程

public class Client {
 
      public static void main(String[] args) {
 
             IDriver zhangSan = new Driver();
 
              ICar bmw = new BMW();
 
              //张三开奔驰车
 
              zhangSan.drive(bmw);
 
      }
 
}
点评: 在新增加底层模块时,只修改了业务场景类,也就是高层模块,对其他模块如Driver类不需要做任何修改,把”变更“的风险降到最低。

依赖倒置对并行开发的影响。两个类有依赖关系,只要指定两者之间的接口(或抽象类)就可以独立开发了,而且项目之间的单元测试也可以独立的运行,而TDD( Test-Driven Development,测试驱动开发) 开发模式就是依赖倒置原则的最高级应用。甲程序员负责IDriver 的开发,乙程序员负责ICar的开发,两个人只要制定好了接口就可以独立开发了,甲开发的快,完成了IDriver以及相关实现类Driver的开发工作,而乙开发滞后,那么甲是否可以进行单元测试(Unit Test)答案是可以的,我们引入一个JMock工具。todo.....


抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不逃脱契约的范畴,确保约束双方按照既定的契约(抽象)共同发展。

依赖的三种写法,或者依赖传递的三种方式?

依赖是可以传递的,A对象依赖B对象,B依赖C,C依赖D....生生不息;记住只要做到依赖 抽象,即使是多层的依赖传递也无所谓!

1. 构造函数传递依赖对象

public interface IDriver{
     public void drive();
}

public class Driver implements IDriver{
    private  ICar car;
   
    public Driver(ICar _car){
          this.car = _car;
    }

     public void drive(){
           this.car.run();
     }
}
2. setter方法传递依赖对象

public interface IDriver{
     public void setCar(ICar car);
     public void drive();
}

public class Driver implements IDriver{
      private  ICar car;
      public void setCar(ICar car){
             this.car = car;
      }

       public void drive(){
             this.car.run();
       }
}

 3. 接口声明依赖对象,也叫 接口注入。

3.4 编码最佳实践

      依赖倒置的本质原则就是 :通过抽象(接口或抽象类)使各个类或模块实现彼此独立,互不影响,实现模块间的松耦合。

1.  每个类尽量都要有接口或抽象类,或者抽象类和接口两者都具备。

2. 变量的显示类型 尽量是接口或者抽象类。

3. 任何类都不应该从具体类派生。  这样不是绝对的如,在项目开发阶段要遵从此原则。要是做维护老项目,基本就是做扩展开发,通过继承关系,覆写一个方法就可以修改一个bug,何必要去继承最高的基类内容

4. 尽量不要覆写基类的方法

5.     结合里氏替换原则。  父类出现的地方子类就能出现


”倒置“怎么理解 ? 依赖正置 ,我要开奔驰就依赖奔驰,面向实现编程,估计这也是90年代主要思想,后来这种方式有很大问题 所以要”倒置“即依赖抽象。

现实世界中,有些必须依赖细节 如:法律,”杀人偿命” 这个算依赖抽象,实际上,要具体情况(依赖细节)才能判决是否要偿命

参考博客http://www.cnblogs.com/cbf4life/archive/2009/12/15/1624435.html

你可能感兴趣的:(面向对象写代码/代码整理,设计模式/代码整理)