依赖倒置、控制反转和依赖注入(一)

来源:互联网 发布:js设置div边框颜色 编辑:程序博客网 时间:2024/05/26 02:20

概述

        在进行WPF开发时,我遇到了Unity Container、IoC、Dependency Injection等。当时我陷入到困惑的思考总,为什么要使用这些。当后来我逐渐的了解这些技术的优点后,我开始意识到了我们实际上是需要他们的。

        本文,我来解释DI和IoC的需求和使用情况,本文分为5个部分。

        1. 依赖倒置原则

        2. 控制反转和控制反转容器(IoC Container)

        3. 自定义控制反转容器(IoC Container)

        4. 自定义支持生命周期的控制反转容器(IoC Container)

        5. 利用Microsoft Unity的依赖注入(Dependency Injection)

前提条件

        最好是最如下内容有所了解

        开闭原则(Open/closed principle)

        接口分离原则

依赖倒置原则(DIP)

        DIP是SOLID原则中的一个,他们由Robert Martin于1992年提出。

        S – Single responsibility principle(单一职责原则)

        O – Open/closed principle(开闭原则)

        L – Liskov  substitution principle(里氏代换原则)

        I – Interface segregation principle(接口分离原则)

        D – Dependency inversion principle(依赖倒置原则)

C. Robert Matrin的依赖倒置原则内容如下:

        a). 高层次的模块不应该依赖低层次的模块,他们都应该依赖于抽象。

        b). 抽象不应该依赖于具体,具体应该依赖于抽象。

DIP指的就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。


图 1

如图1所示,“Copy”程序(高层次的模块)会调用“Read Keyboard”和“Write Printer”。因此“Copy”依赖“Read Keyboard”和“Write Printer”,并且存在紧耦合。

    public class Copy    {        publicvoidDoWork()        {            ReadKeyboardreader =new ReadKeyboard();            WritePrinterwriter =new WritePrinter();             string data= reader.ReadFromKeyboard();            writer.WriteToPrinter(data);        }    }

        上述代码看起来还不错,但是当我们需要追加更多的Reader或者Writer对象时,在上述代码结构下,我们需要修改“copy”程序,增加创建新Reader或Writer对象,并且要追加条件判断语句,来选择用哪个Reader或Writer来执行操作。但是这么做的话,违背了面向对象的开闭原则。

        例如,我们扩展“Copy”程序,如图2,此时我们的“copy”程序可以从扫描器读数据并能够将数据写入到磁盘。


图 2

        在这种情况下,我们需要调整我们的“copy”程序

    public class Copy    {        publicvoidDoWork()        {            string data=string.Empty;            switch(readerType)            {                case"keyboard":                    ReadKeyboardreader = newReadKeyboard();                    data =reader.ReadFromKeyboard();                    break;                case"scanner":                    ReadScannerreader2 = newReadScanner();                    data =reader2.ReadFromScanner();                    break;            }            switch(writerType)            {                case"printer":                    WritePrinterwriter = newWritePrinter();                   writer.WriteToPrinter(data);                    break;                case"flashdisk":                    WriteFlashDiskwriter2 = newWriteFlashDisk();                    writer2.WriteToFlashDisk(data);                    break;            }        }         privatestringreaderType;        privatestringwriterType;    }
        根据上面的场景,如果需要继续追加新的Reader和Writer,我们就需要修正copy程序,因此copy严重的依赖具体的Reader和Writer对象。

        为了解决上述问题,我们可以调整我们的“copy”程序,使其依赖抽象的接口而不是具体的实现。下图解释了依赖倒置。


图 3


图 4

在图3、4中,“Copy”程序依赖两个抽象的接口IReader和IWriter,这样低级别的组件就被抽象成了接口。

如下例子,在上述的图中,可以看出,类“ReadKeyboard”派生自接口IReader,类“WritePrinter”派生自接口IWriter,因此“Copy”程序调用IReader和IWriter就执行具体的操作了。因此如果我们想要追加更多的底层组件,例如“Scanner”和“FlashDisk”,我们可以创建派生自“IReader”和“IWriter”的新类即可。

    public interface IReader    {        stringRead();    }     public interface IWriter    {        voidWrite(string data);    }     public class ReadKeyboard :IReader    {        publicstringRead()        {            // code to read from keyboardand return as string            returnstring.Empty;        }    }     public class ReadScanner :IReader    {        publicstringRead()        {            // code to read from scannerand return as string            returnstring.Empty;        }    }     public class WritePrinter :IWriter    {        publicvoidWrite(string data)        {            // code to write to theprinter        }    }     public class WriteFlashDisk :IWriter    {        publicvoidWrite(string data)        {            // code to write to the flashdisk        }    }     public class Copy    {        privatestring_readerType;        privatestring_writerType;         public Copy(stringreaderType,string writerType)        {            _readerType = readerType;            _writerType = writerType;        }         publicvoidDoWork()        {            IReaderreader =null;            IWriterwriter =null;            string data;            switch(readerType)            {                case"keyboard":                    reader = new ReadKeyboard();                    break;                case"scanner":                    reader = new ReadScanner();                    break;            }             switch(writerType)            {                case"printer":                    writer = new WritePrinter();                    break;                case"flashdisk":                    writer = new WriteFlashDisk();                    break;            }             data = reader.Read();            writer.Write(data);        }    }

        在这种情况下,虽然具体的读写操作已经被抽象出来的,但是仍然存在高层模块依赖底层模块的现象,因为我们在高层模块中明确的实例化了具体的底层类对象。因此这种情况下,当追加新的类时,代码中的swith、case处仍然需要不断的维护。因此上述的修改方案并未完全的满足DIP原则。

        为了彻底的移除依赖,我们需要在高层组件外面来创建依赖对象,并且依赖对象中存在某种机制能够传递到依赖模块中。

        现在新的问题来了,如何实现依赖反转(Dependency Inversion)

        其中一个方法是控制反转(Inversion ofControl, IoC),相当于如下代码:

    public class Copy    {               publicvoidDoWork()        {            IReaderreader = serviceLocator.GetReader();            IWriterwriter = serviceLocator.GetWriter();            string data= reader.Read();            writer.Write(data);        }    }
        上述代码替代了实例化Reader和Writer对象的代码,上述代码已经将创建Reader和Writer对象的处理转移到servicelocator中。从而使得“copy”程序不必因为底层组件的增减而修改了。

        依赖注入(DependencyInjection)是实现控制反转的一种机制,在下一部分我会介绍什么是控制反转(Inversionof Control, IoC)及利用不同的机制实现依赖倒置原则(其中依赖注入是其中的一种办法)。

小结

        本文介绍了依赖倒置原则,及其在真实的使用场景。在后面的文章中我会介绍控制反转(Inversionof Control,IoC)和依赖注入(DependencyInjection,DI)。

原文地址

http://www.codeproject.com/Articles/465173/Dependency-Inversion-Principle-IoC-Container-Depen

0 0
原创粉丝点击