概述
上层模块不应该依赖于下层模块,二者都应该依赖于抽象。
抽象不应该有具体的实现,但是具体的实现应当依赖于抽象。
描述
在系统架构分析和设计中,软件的创建总是会带有一些错误的依赖关系,比如说业务逻辑依赖于底层实现,这种情形往往会导致业务逻辑变更,下层实现随之改变。当然,反过来也是成立的。因此,我们需要将彼此的依赖关系进行反转,这也是依赖倒置原则被引入的原因。
下面我们以按钮和灯的关系来进行讲解。
一般情况下,我们会这样的组织二者之间的关系:
using System;namespace DependencyInversionDaemon{ class Light { public void TurnOn() { Console.WriteLine("The light is on."); } public void TurnOff() { Console.WriteLine("The light is off."); } }}
namespace DependencyInversionDaemon{ class ButtonEx { private Light light = new Light(); private bool pressed = false; public void Press() { if (pressed) light.TurnOn(); else light.TurnOff(); pressed = !pressed; } }}
Light类被ButtonEx类控制,这是典型的耦合度过高的设计。控制的逻辑部分在ButtonEx中,而下层的扩展则在Light类中,所以,ButtonEx类属于上层扩展部分而Light类则属于下层扩展部分。
所以,依赖方向是从ButtonEx类流向了Light类。如果Light类需要进行扩展,那么ButtonEx类将会受到影响。这就意味着,下层需求部分的改变将会影响上层应用部分。
重构
理想情况下,业务逻辑将会决定下层代码如何扩展,所以,下层模块应该依赖于业务逻辑。
但是,通过依赖倒置原则,我们应该讲ButtonEx类和Light类进行解耦,解耦的方式就是引入一个虚类,这个虚类能够将Light类中的具体行为抽象出来。
现在,我们在类中新加入一个Switchable类,这个类包含了ButtonEx类所需要的各种操作(也就是上层扩展中所需要的各种操作功能,比如TurnOn,TurnOff等)。ButtonEx类和Light类将不会直接的进行调用,而是通过这个Switchable中间类实现依赖倒置。
namespace DependencyInversionDaemons{ interface Switchable { void TurnOn(); void TurnOff(); }}
using System;namespace DependencyInversionDaemons{ class Light:Switchable { public void TurnOn() { Console.WriteLine("The light is on."); } public void TurnOff() { Console.WriteLine("The light is off."); } }}
using System;namespace DependencyInversionDaemons{ class Fridge:Switchable { public void TurnOn() { Console.WriteLine("The fridge is on."); } public void TurnOff() { Console.WriteLine("The fridge is off."); } }}
namespace DependencyInversionDaemons{ class ButtonEx { private Switchable swithableObjects; private bool pressed = false; public void SetSwitchable(Switchable switchable) { this.swithableObjects = switchable; } public void Press() { if (pressed) swithableObjects.TurnOn(); else swithableObjects.TurnOff(); pressed = !pressed; } }}
using System;namespace DependencyInversionDaemons{ class Program { static void Main(string[] args) { ButtonEx button = new ButtonEx(); button.SetSwitchable(new Fridge()); button.Press(); button.Press(); Console.ReadKey(); } }}
现在,当我们向这个工程里添加其他的设备的时候,就不需要改变ButtonEx类中的任何代码,这种依赖关系已经被解耦了。这种设计符合面向对象原则:松耦合,高内聚。
最后需要补充一点就是,依赖倒置原则,其实需要我们不要为实现而编程,要为接口编程。