面向对象设计原则之4-依赖倒置原则

高层模块不应该依赖低层模块,他们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。简单的定义为:面向接口(抽象)编程,不要面向实现编程。

C#设计模式概述

依赖倒置原则Dependence Inversion Principle DIP

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

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

DIP:High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.

Program to an interface, not an implementation.

什么是高层模块?简单地说,就是封装的层级高,我们就认为其是高层模块。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();

}

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

本文由 .Net中文网 原创发布,欢迎大家踊跃转载。

转载请注明本文地址:https://www.byteflying.com/archives/345

发表评论

登录后才能评论

评论列表(2条)

  • 八月
    八月 2020年07月28日 21:57

    举得例子很容易让人理解,并且举了依赖注入的三种方式的例子 ,很详细