【愚公系列】2021年12月 面向对象设计原则(四)-依赖倒置原则(Dependence Inversion Principle DIP)

2021-12-03 16:08:19 浏览数 (1)

文章目录

  • 前言
  • 一、依赖倒置原则(Dependence Inversion Principle DIP )
  • 二、使用步骤
    • 示例
  • 总结

前言

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

提示:以下是本篇文章正文内容,下面案例可供参考

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

高层模块不应该依赖低层模块,他们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

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

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

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

二、使用步骤

示例

代码语言:javascript复制
public class XiaoMiPhone {

    public bool Unlock() => true;

}
代码语言:javascript复制
public class Customer {

    public bool UnlockPhone(XiaoMiPhone phone) => phone.Unlock();

}
代码语言:javascript复制
var customer = new Customer();
var phone = new XiaoMiPhone();

var lockResult = customer.UnlockPhone(phone);

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

代码语言:javascript复制
public interface IMobilePhone {

    bool Unlock();

}
代码语言:javascript复制
public class XiaoMiPhone : IMobilePhone {

    public bool Unlock() {
        Console.WriteLine("Use fingerprint to unlock your phone!");
        return true;
    }

}
代码语言:javascript复制
public class ApplePhoneX : IMobilePhone {

    public bool Unlock() {
        Console.WriteLine("Use Face ID to unlock your phone!");
        return true;
    }

}
代码语言:javascript复制
public class Customer {

    public bool UnlockPhone(IMobilePhone phone) => phone.Unlock();

}
代码语言:javascript复制
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的相关文章中为大家详细介绍。

构造注入:

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

设值注入:

代码语言:javascript复制
public class Customer {

    public IMobilePhone Phone { get; set; }
    public bool UnlockPhone() => Phone.Unlock();

}

接口注入:

代码语言:javascript复制
interface IPhoneProvider {

    IMobilePhone Phone { get; set; }

}
代码语言:javascript复制
public interface IMobilePhone {

    bool Unlock();

}
代码语言:javascript复制
public class Custome : IPhoneProvider {

    public IMobilePhone Phone { get; set; }
    public bool UnlockPhone() => Phone.Unlock();

}

总结

综上所述,我们不难得到结论,注入是手段,依赖倒置是目的。

0 人点赞