06.依赖倒置原则介绍

06.依赖倒置原则介绍

目录介绍
  • 01.问题思考的分析
  • 02.学习依赖倒置目标
  • 03.理解依赖倒置原则
  • 04.依赖倒置原则思想
  • 05.多数据库操作案例
  • 06.用户购买家电案例
  • 07.发送消息的案例
  • 08.依赖倒置VS依赖注入
  • 09.依赖倒置的思考
  • 10.依赖倒置原则总结

01.问题思考的分析

依赖倒置原则。单一职责原则和开闭原则的原理比较简单,但是,想要在实践中用好却比较难。而今天要讲到的依赖倒置原则正好相反。

这个原则用起来比较简单,但概念理解起来比较难。比如,下面这几个问题,你看看能否清晰地回答出来:

  1. "依赖倒置"这个概念指的是"谁跟谁"的"什么依赖"被反转了?"倒置"两个字该如何理解?
  2. 经常听到另外一个概念:依赖注入。这两个概念跟"依赖倒置"有什么区别和联系呢?它们说的是同一个事情吗?

02.学习依赖倒置目标

  1. 搞懂依赖倒置原则,为什么依赖倒置是重要的,以及它如何帮助我们构建灵活、可扩展和可维护的软件系统。
  2. 掌握依赖倒置的实现方式,依赖注入、工厂模式、策略模式等常见的实现方式,以及它们的优缺点和适用场景。
  3. 应用依赖倒置到实际项目:通过实际项目的练习和实践,将依赖倒置应用到实际的软件开发中。

03.理解依赖倒置原则

依赖倒置原则是面向对象设计的六大原则之一,它的定义是:

  1. 高层模块不应该依赖于低层模块。两者都应该依赖于抽象。
  2. 抽象不应该依赖于细节。细节应该依赖于抽象。

换句话说,设计中应当依赖于接口或抽象类,而不是依赖于具体实现。这种设计有助于减少模块之间的耦合,增加系统的灵活性和可维护性。

04.依赖倒置原则思想

依赖倒置原则的核心思想是面向抽象编程而不是面向具体编程。通过依赖抽象层次(如接口、抽象类),可以让高层模块不依赖具体的实现细节,从而使得系统更具扩展性和灵活性。

05.多数据库操作案例

例子1:违反依赖倒置原则。假设我们有一个UserService类,它直接依赖于MySQLDatabase类来进行数据操作。

java 复制代码
class MySQLDatabase {
    public void saveUser(String username) {
        // 保存用户到MySQL数据库
        System.out.println("Saving " + username + " to MySQL database.");
    }
}

class UserService {
    private MySQLDatabase database = new MySQLDatabase();

    public void addUser(String username) {
        database.saveUser(username);
    }
}

在这个例子中,UserService类直接依赖于MySQLDatabase类,这是对具体实现的依赖,违反了依赖倒置原则。

如果我们需要将数据库换成PostgresSQLDatabase,或者换成AliSQLDatabase,我们必须修改UserService类,这增加了系统的耦合度和修改的风险。

例子2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。

java 复制代码
// 抽象层:定义一个接口,供不同的数据库实现
interface Database {
    void saveUser(String username);
}

// 具体实现:MySQL数据库
class MySQLDatabase implements Database {
    public void saveUser(String username) {
        System.out.println("Saving " + username + " to MySQL database.");
    }
}

// 具体实现:PostgreSQL数据库
class PostgreSQLDatabase implements Database {
    public void saveUser(String username) {
        System.out.println("Saving " + username + " to PostgreSQL database.");
    }
}

// 高层模块:UserService类依赖于抽象接口,而不是具体实现
class UserService {

    private Database database;

    public UserService(Database database) {
        this.database = database;
    }

    public void addUser(String username) {
        database.saveUser(username);
    }
}

在这个例子中,UserService类依赖于Database接口,而不是具体的数据库实现。

这意味着如果我们想更换数据库,只需要传入不同的实现类,而不需要修改UserService类本身。这样做遵循了依赖倒置原则,降低了模块之间的耦合度。

从这个例子可知,定义数据库抽象层供不同的数据库实现,然后在UserService类依赖于抽象接口而不是具体实现,可以充分降低代码耦合度,可以快速拓展其他数据库实现。

06.用户购买家电案例

例子1:违反依赖倒置原则。假设顾客,想要买冰箱,洗衣机,电视,或者其他电器,它直接依赖于Customer类来进行数据操作。

java 复制代码
public class Customer {
    public void buyFridge() {
        System.out.println("购买冰箱");
    }

    public void buyTelevision() {
        System.out.println("购买电视");
    }
}

例子2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。

java 复制代码
/**
 * 商品接口
 */
public interface IGood {
    /**
     * 购买商品
     */
    void buy();
}

public class Customer {
    public void buy(IGood iGood) {
        iGood.buy();
    }
}

/**
 * 冰箱商品
 */
public class FridgeGood implements IGood {
    @Override
    public void buy() {
        System.out.println("购买冰箱");
    }
}

/**
 * 电视商品
 */
public class TelevisionGood implements IGood {
    @Override
    public void buy() {
        System.out.println("购买电视");
    }
}

/**
 * 洗衣机商品
 */
public class WashMachineGood implements IGood {
    @Override
    public void buy() {
        System.out.println("购买洗衣机");
    }
}

public class Main {
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.buy(new FridgeGood());
        customer.buy(new TelevisionGood());
        customer.buy(new WashMachineGood());
    }
}

07.发送消息的案例

例子1:违反依赖倒置原则。假设用户,可以通过邮件,短信息,信件等发送消息,下面这种就不太友好。

java 复制代码
public class Notification {
    public void email(String cellphone, String message) {
        System.out.println("通过邮件发送消息");
    }

    public void message(String cellphone, String message) {
        System.out.println("通过短信息发送消息");
    }
    
    public void letter(String cellphone, String message) {
        System.out.println("通过信件发送消息");
    }
}

例子2:遵循依赖倒置原则。可以通过引入一个抽象层来遵循依赖倒置原则。

java 复制代码
public class Notification {
  private MessageSender messageSender;
  
  public Notification(MessageSender messageSender) {
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    this.messageSender.send(cellphone, message);
  }
}

public interface MessageSender {
  void send(String cellphone, String message);
}

// 邮件发送类
public class EmailSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

// 短信发送类
public class SmsSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

// 站内信发送类
public class InboxSender implements MessageSender {
  @Override
  public void send(String cellphone, String message) {
    //....
  }
}

08.依赖倒置VS依赖注入

8.1 理解依赖注入

‌依赖注入(Dependency Injection, DI)是一种设计模式,旨在将对象的依赖关系从对象内部转移到外部管理,从而降低类之间的耦合度,提高代码的可维护性和可测试性‌。‌

8.2 依赖注入方式

依赖注入(Dependency Injection,DI)是一种通过将依赖关系从高层模块解耦的方式,常用于实现依赖倒置原则。

  1. 构造函数注入:通过在类的构造函数中声明依赖参数,将依赖关系通过构造函数传递给类的实例。最常见的依赖注入方式。
  2. Setter方法注入:通过提供一组setter方法,允许外部代码设置依赖对象。这种方式相对于构造函数注入更灵活,可以在对象创建后随时更改依赖。
  3. 接口注入:通过在类中定义一个接口,该接口包含用于注入依赖的方法。类实现该接口,并通过接口方法接收依赖对象。这种方式相对较少使用,因为它引入了更多的接口和方法。
  4. 属性注入:通过在类中声明依赖对象的属性,并提供相应的setter方法,将依赖对象注入到属性中。可能导致类的实例在没有依赖对象的情况下被创建。

8.3 两者有何区别

依赖倒置原则(Dependency Inversion Principle,DIP)和依赖注入(Dependency Injection,DI)是面向对象设计中两个相关但不同的概念。

  1. 依赖倒置原则是一种设计原则,它指导我们在设计软件时应该依赖于抽象而不是具体实现。高层模块不应该直接依赖于低层模块,而是通过抽象接口或基类来进行依赖。
  2. 依赖注入是一种实现依赖倒置原则的具体技术,它通过将依赖关系从高层模块解耦,将依赖对象注入到类的实例中。依赖注入有多种方式,如构造函数注入、setter方法注入、属性注入等。它的目的是通过外部注入依赖对象,而不是在类内部创建或获取依赖对象。

依赖注入是实现依赖倒置原则的一种常见方式,但并不是唯一的方式。依赖倒置原则还可以通过工厂模式、策略模式等其他设计模式来实现。

09.依赖倒置的思考

9.1 依赖倒置优点

  1. 降低耦合性:高层模块和低层模块之间通过抽象进行解耦,避免了对具体实现的依赖。
  2. 增强可扩展性:通过依赖抽象,可以更容易地替换或扩展具体实现,而不需要修改高层模块。
  3. 提高可维护性:模块之间的解耦使得系统的维护变得更加容易,修改一个模块的实现不会影响到其他模块。

9.2 依赖倒置缺点

  1. 增加系统的复杂性:为了遵循依赖倒置原则,通常需要引入额外的抽象层次,这可能会增加系统的复杂性。
  2. 可能导致过度设计:在简单系统中,如果过度使用依赖倒置原则,可能导致不必要的复杂性,造成过度设计。

9.3 在设计模式的体现

  1. 工厂模式(Factory Pattern):通过定义抽象工厂接口,让具体的工厂类实现该接口,从而将对象的创建过程与使用过程解耦。这样,高层模块只依赖于抽象工厂接口,而不依赖于具体的产品类。这符合依赖倒置原则,使得高层模块依赖于抽象而非具体实现。
  2. 观察者模式(Observer Pattern):观察者依赖于被观察者的抽象接口,而不依赖于具体的被观察者。这样,当被观察者发生变化时,观察者可以接收到通知并做出相应的处理,而不需要直接依赖于具体的被观察者。
  3. 策略模式(Strategy Pattern):通过定义一个抽象策略接口,让具体的策略类实现该接口,从而将算法的选择与使用解耦。高层模块通过依赖于抽象策略接口,而不依赖于具体的策略类,实现了依赖倒置原则。

这些设计模式的共同点是,它们都通过引入抽象接口或基类,将高层模块与具体实现解耦,使得高层模块依赖于抽象而非具体实现。符合依赖倒置原则的设计思想。

10.依赖倒置原则总结

  1. 依赖倒置问题思考:"依赖倒置"这个概念指的是"谁跟谁"的"什么依赖"被反转了?"倒置"两个字该如何理解?
  2. 学习该原则目标:搞懂该原则作用,掌握实现方式,并且应用到实际案例项目中。
  3. 依赖倒置的定义:高层模块不应该依赖底层模块,应该依赖于抽象。换句话说,设计中应当依赖于接口或抽象类,而不是依赖于具体实现。
  4. 依赖倒置原则思想:通过依赖抽象层次(如接口、抽象类),可以让高层模块不依赖具体的实现细节,从而使得系统更具扩展性和灵活性。
  5. 多数据库操作案例:从这个例子可知,定义数据库抽象层供不同的数据库实现,然后在UserService类依赖于抽象接口而不是具体实现,可以充分降低代码耦合度,可以快速拓展其他数据库实现。
  6. 依赖注入如何理解:依赖注入(Dependency Injection,DI)是一种通过将依赖关系从高层模块解耦的方式,常用于实现依赖倒置原则。
  7. 依赖注入有哪些方式:1.构造函数注入;2.Setter方法注入;3.接口注入;4.属性注入
  8. 依赖倒置和依赖注入区别:依赖倒置原则是一种设计原则,依赖注入是一种实现依赖倒置原则的具体技术。
  9. 依赖倒置有哪些缺点:为了遵循依赖倒置原则,通常需要引入额外的抽象层次,这可能会增加系统的复杂性。
  10. 在设计模式的体现有哪些:工厂模式,观察者模式,策略模式,这些设计模式的共同点是,它们都通过引入抽象接口或基类,将高层模块与具体实现解耦,使得高层模块依赖于抽象而非具体实现。符合依赖倒置原则的设计思想。

11.更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android
相关推荐
Seven971 小时前
【设计模式】使用中介者模式实现松耦合设计
java·后端·设计模式
Seven971 小时前
【设计模式】探索状态模式在现代软件开发中的应用
java·后端·设计模式
Seven971 小时前
【设计模式】从事件驱动到即时更新:掌握观察者模式的核心技巧
java·后端·设计模式
Seven972 小时前
【设计模式】责任链模式教你如何优雅地分发任务
java·后端·设计模式
Seven972 小时前
【设计模式】命令模式助力快速添加新命令而不影响现有代码
java·后端·设计模式
这里有鱼汤2 小时前
程序员必看!突破编程瓶颈!这3种设计模式让你的Python项目飞起来
后端·python·设计模式
vker3 小时前
责任链 vs 规则树:如何构建更强大的规则引擎?
后端·设计模式·代码规范
牵牛老人3 小时前
C++设计模式-抽象工厂模式:从原理、适用场景、使用方法,常见问题和解决方案深度解析
c++·设计模式·抽象工厂模式
液态不合群3 小时前
【设计模式】从火车站卖票看代理模式的实际应用
设计模式·代理模式
谢道韫6663 小时前
23种设计模式
设计模式