YAZONG 我的开源

设计模式-六大设计原则(三)-依赖倒置原则

 
0 评论0 浏览

1、3依赖倒置原则

定义

高层模块不应该依赖于底层模块,二者都应该依赖其抽象;
抽象不应该依赖细节;
细节应该依赖抽象。

高层模块和底层模块容易理解,每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是底层模块,原子逻辑的再组装就是高层模块。那什么是抽象?什么又是细节呢?在java语言中,抽象就是指接口或抽象类,两个都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。

Java中的定义

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

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

面向接口编程
https://www.cnblogs.com/MQ-zhang/articles/1534398.html
https://www.cnblogs.com/MQ-zhang/category/202126.html

OOD OOP
https://www.cnblogs.com/gentleman-cklai/p/6921489.html
https://www.cnblogs.com/zzyoucan/p/3576932.html

案例

案例1-1

图形结构

image.png

代码示例

public class Client1 {
    public static void main(String[] args) {
        Mother mother = new Mother();
        mother.narrate(new Book());
    }
}
class Book{
    public String getContent(){
        return "很久很久以前有一个阿拉伯的故事……";
    }
}
class Mother{
    public void narrate(Book book){
        System.out.println("妈妈开始讲故事");
        System.out.println(book.getContent());
    }
}

引发的问题

如果上述的类中,使Mother增加阅读其他刊物的对象,那么就需要修改Mother这个类,从而导致程序易变性。什么是稳定性?固化的、健壮的才是稳定的。
设计是否就别稳定性,只要适当地“松松土”,观察“设计的蓝图”是否还可以茁壮地成长就可以得出结论,稳定性较高的设计,在周围环境频繁变化的时候,依然可以做到“我自岿然不动”。
一个项目时一个团队协作的结果,要协作就要并行开发,要并行开发就要解决模块之间的项目依赖关系,那么依赖倒置原则就可以解决此类问题。
什么是并行开发的风险?并行开发最大的风险就是风险扩散,本来只是一段程序的错误或异常,逐步波及一个功能,一个模块,甚至到最后毁坏了整个项目。

案例1-2(优化)

图形结构

image.png

代码示例

public class Client2 {
    public static void main(String[] args) {
        Mother mother = new Mother();
        mother.narrate(new Book());
        mother.narrate(new Newspaper());
    }
}
interface IReader{
    String getContent();
}
class Book implements IReader{
    public String getContent(){
        return "读书";
    }
}
class Newspaper implements IReader{
    public String getContent(){
        return "看报纸";
    }
}
class Mother{
    public void narrate(IReader iReader){
        System.out.println("妈妈开始" + iReader.getContent());
}
}

引发的问题

当然上述Mother这个类也可以实现某个接口,比如IPerson,当Client2中的Mother使用IPerson代替时,IPerson person = new Mother(),person的表面类型是IPerson,是一个接口,是抽象的、非实体化的,在其后的所有操作中,person都是以IPerson类型进行操作,屏蔽了细节对抽象的影响。
当添加了低层模块,比如杂志时,在Client2中只需修改了业务场景类IReader,也就是高层模块,对其他低层模块类,不需要做任何修改,业务就可以正常运行,这样就把“变更”引起的风险扩散降到了最低。
注意,在java中,只要定义变量就必然要有类型,一个变量可以有两种类型:表面类型和实际类型,表面类型是在定义的时候赋予的类型,实际类型是对象的类型,比如:
IPerson person = new Mother(),person的表面类型是IPerson,实际类型是Mother。
抽象是对实现的约束,对依赖者而言,也是一种契约,不仅仅约束自己,还同时约束自己与外部的关系,其目的是保证所有的细节不脱离契约的范畴,确保约束双方按照既定的契约(抽象)共同发展,只要抽象这根基线在,细节就脱离不了这个圈圈,始终让你的对象做到“言必信,行必果”。

依赖的三种写法

构造函数传递依赖对象

public class Client5 {
    public static void main(String[] args) {
        IDriver driver = new Driver(new Benz());
        driver.drive();
    }
}
interface IDriver{
    void drive();
}
class Driver implements IDriver{
 private ICar iCar;  
  
 public Driver(ICar iCar) {  
 this.iCar = iCar;  
 }  
  
 public void drive() {  
 this.iCar.run();  
 }
}
interface ICar{
    void run();
}
class Benz implements ICar{
    public void run() {
        System.out.println("奔驰跑...");
    }
}

Setter方法传递依赖对象

public class Client6 {
    public static void main(String[] args) {
        IDriver driver = new Driver();
        driver.setCar(new Benz());
        driver.drive();
    }
}
interface IDriver{
    void setCar(ICar iCar);
    void drive();
}
class Driver implements IDriver{
 private ICar iCar;  
  
 public void setCar(ICar iCar) {  
 this.iCar = iCar;  
 }  
  
 public void drive() {  
 this.iCar.run();  
 }
}
interface ICar{
    void run();
}
class Benz implements ICar{
    public void run() {
        System.out.println("奔驰跑...");
    }
}

接口声明依赖对象

public class Client4 {
    public static void main(String[] args) {
        IDriver driver = new Driver();
        driver.drive(new Benz());
        driver.drive(new Bmw());
    }
}
interface IDriver{
    void drive(ICar iCar);
}
class Driver implements IDriver{
    public void drive(ICar iCar) {
        iCar.run();
    }
}
interface ICar{
    void run();
}
class Benz implements ICar{
    public void run() {
        System.out.println("奔驰跑............");
    }
}
class Bmw implements ICar{
    public void run() {
        System.out.println("宝马跑............");
    }
}

总结

依赖倒置原则的核心思想是面向接口编程。

依赖倒置原则依赖于这样一个事实,相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比为基础搭建起来的架构要稳定的多。

依赖倒置原则可降低类之间的耦合性,增加系统的稳定性,降低并发开发引起的风险,增加代码的可读性和可维护性。

依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,使用规则:
每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备(基本要求);
变量的表面类型尽量是接口或是抽象类;
任何类都不应该从具体类派生(只要不超过两层的继承都是可以接受的;维护项目不要去继承最高层的基类,覆写一个已存在的类的方法即可);
尽量不要覆写基类的方法(已经覆写过的再次覆写会降低依赖的稳定性);
结合里氏替换原则使用(通俗的规则:接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化);

“依赖正置”就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程。

依赖倒置原则的优点在小型项目中很难体现出来,但是在一个大型项目中,采用依赖倒置原则有非常多的优点,特别是规避一些非技术因素引起的问题。项目越大,需求变化的概率也越大,可以减少需求变化引起的工作量剧增的情况。

依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。

在实际项目中使用依赖倒置原则时需要审时度势,不要抓住一个原则不放,每一个原则的优点都是有限度的,不要为了遵循一个原则而放弃了一个项目的终极目标:投产上线和盈利。


标题:设计模式-六大设计原则(三)-依赖倒置原则
作者:yazong
地址:https://blog.llyweb.com/articles/2020/03/19/1584566862768.html