写点什么

【愚公系列】2022 年 06 月 面向对象设计原则 (四)- 依赖倒置原则

作者:愚公搬代码
  • 2022 年 6 月 09 日
  • 本文字数:2122 字

    阅读完需:约 7 分钟

前言

常用的面向对象设计原则有七个,这七大设计原则都是以可维护性和可复用性为基础的,这些原则并不是孤立存在的,它们相互依赖相互补充,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性。

一、依赖倒置原则(Dependence Inversion Principle DIP )

<font color=#999AAA >高层模块不应该依赖低层模块,他们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。


简单的定义为:面向接口(抽象)编程,不要面向实现编程。


什么是高层模块?简单地说,就是封装的层级高,我们就认为其是高层模块。Customer 类是一个客户类,该客户包含 UnlockPhone 解锁手机方法,该方法需要传递一个 XiaoMiPhone 的手机类以便解锁手机,那么 Customer 类就是高层模块,XiaoMiPhone 类就是低层模块。


什么是细节?细节就是实例方法,是一个完整的、足够小的逻辑单元,是一段包含代码的子程序。什么是抽象?在 C#中,抽象就是抽象类(准确地说,应该是抽象类中的抽象方法,因为抽象类中可以包含实例方法)或接口,他们都无法被直接实例化,只能通过抽象类的子类、接口的实现类或工厂方法提供实例(容器也可以提供实例,但其本质上仍是工厂)。实际上抽象根本无法依赖细节,因为 C#语法规定,抽象方法和接口无法包含实现,即不可能包含细节,这就是“抽象不应该依赖细节”。那么什么是“细节应该依赖抽象”呢?细节应该依赖抽象可以认为是里氏替换原则的升级版,它要求尽可能的使用抽象基类或接口作为方法的参数。

二、使用步骤

示例

public class XiaoMiPhone {
public bool Unlock() => true;
}
复制代码


public class Customer {
public bool UnlockPhone(XiaoMiPhone phone) => phone.Unlock();
}
复制代码


var customer = new Customer();var phone = new XiaoMiPhone();
var lockResult = customer.UnlockPhone(phone);
复制代码


通过上面的代码我们可以明显看到,高层模块 Customer 类严重依赖低层模块 XiaoMiPhone 类,因为 UnlockPhone 方法需要一个 XiaoMiPhone 类的参数,这种强依赖关系导致的一个后果是,无论修改了 Customer 类还是 XiaoMiPhone 类,都无法保证调用方一定可以正确运行,我们需要对这 2 个类做完整的回归测试。另外一个问题是,有一天我们想解锁 IphoneX,将要对以上代码进行大规模的修改,这显然违背了开闭原则。以下给出一个解决方案以供参考:


public interface IMobilePhone {
bool Unlock();
}
复制代码


public class XiaoMiPhone : IMobilePhone {
public bool Unlock() { Console.WriteLine("Use fingerprint to unlock your phone!"); return true; }
}
复制代码


public class ApplePhoneX : IMobilePhone {
public bool Unlock() { Console.WriteLine("Use Face ID to unlock your phone!"); return true; }
}
复制代码


public class Customer {
public bool UnlockPhone(IMobilePhone phone) => phone.Unlock();
}
复制代码


var customer = new Customer();
IMobilePhone phone = new XiaoMiPhone();var lockResult = customer.UnlockPhone(phone);
phone = new ApplePhoneX();lockResult = customer.UnlockPhone(phone);
复制代码


首先通过 IMobilePhone 建立契约,提供 Unlock 方法,XiaoMiPhone 和 ApplePhoneX 类实现 IMobilePhone 接口,高层模块 Customer 不再依赖某一确定的手机类,而是依赖于 IMobilePhone 接口,即高层模块依赖于抽象。那么低层模块呢?本例中的低层模块为具体的手机类,它并不依赖任何模块,高、低层模块是相对的概念,实际开发过程中低层模块 ApplePhoneX 可能依赖于其它更低层的模块以便提供更多的功能,对于这个更低层的模块,ApplePhoneX 变成了它的高层模块,毕竟“生命不息,依赖不止”。


通过上面的分析我们不难发现,本来高层模块依赖低层模块,经过代码改造后,变成了它们都依赖于抽象,即依赖发生了转移,这就是所谓的“依赖倒置原则”。实现依赖倒置的方式称为依赖注入(Dependency Injection),常见的依赖注入方式有 3 种,构造注入,设值注入、接口注入。


注:另外还有一种服务定位器注入的方式,这将在以后 Asp.Net 的相关文章中为大家详细介绍。


构造注入:


public class Customer {    private IMobilePhone _phone = null;    public Customer(IMobilePhone phone) { _phone = phone; }    public bool UnlockPhone() => _phone.Unlock();}
复制代码


设值注入:


public class Customer {
public IMobilePhone Phone { get; set; } public bool UnlockPhone() => Phone.Unlock();
}
复制代码


接口注入:


interface IPhoneProvider {
IMobilePhone Phone { get; set; }
}
复制代码


public interface IMobilePhone {
bool Unlock();
}
复制代码


public class Custome : IPhoneProvider {
public IMobilePhone Phone { get; set; } public bool UnlockPhone() => Phone.Unlock();
}
复制代码


综上所述,我们不难得到结论,注入是手段,依赖倒置是目的。<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

总结

依赖倒置原则的主要作用如下。


  1. 依赖倒置原则可以降低类间的耦合性。

  2. 依赖倒置原则可以提高系统的稳定性。

  3. 依赖倒置原则可以减少并行开发引起的风险。

  4. 依赖倒置原则可以提高代码的可读性和可维护性。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2022.03.01 加入

该博客包括:.NET、Java、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、python、大数据等相关使用及进阶知识。查看博客过程中,如有任何问题,皆可随时沟通。

评论

发布
暂无评论
【愚公系列】2022年06月 面向对象设计原则(四)-依赖倒置原则_6月月更_愚公搬代码_InfoQ写作社区