此随笔的重点在“Demo分析”一章,以代码的分阶段变化讲述了DI,DIP,IOC的演变,写在前面文字均为铺垫。

希望各位园友拍砖,促使流浪者的进步,现在有很多问题想讨论,即以此文寻找志同道合的园友,另此文草草写作,发现越写越觉得还有很多没有描述出来,暂且如此,等待过些时日再来总结,草草之作难免有错误之处,忘各位园友斧正,不甚感激!

先来个简介,看不懂没关系,按着顺序看下去等Demo看完就明白了,简介只是为了有个宏观的概念,强烈建议看完Demo分析后再重读一遍:

1. DI: 有了OO,抽象封装为一个个的类,这样类之间便有了依赖关系,为了使各个组件(类)之间解耦(将new实例转移出类),不在“高层组件”内初始化“低层组 件”,而是通过各种方式将“低层组件”注入到“高层组件”内,依赖注入(Dependency Injection)出现了。

2. DIP: 依赖倒置原则(Dependency Inversion Principle),何为依赖倒置?下面几步是关键(首先假设我们有“高层组件”和“低层组件”两个类库DLL):

1)第一阶段,“高层组件”包含(Has-A)“低层组件”。没有注入,“高层组件”严重依赖“低层组件”。

2)第二阶段,“高层组件”包含(Has-A)“低层组件”的抽象父类或接口,接口在“低层组件”中定义。构造方法,接口,或Set属性实现注入,“高层组件”与“低层组件”耦合转到抽象上,解除了耦合。此时客户端调用时仍然使用new关键字实现“低层组件”并注入“高层组件”。

3)第三阶段,“高层组件”包含(Has-A)“低层组件”的抽象父类或接口,接口在“高层组件”中定义。构造方法,接口,或Set属性实现注入,“高层组件”与“低层组件”耦合转到抽象上,解除了耦合。此时客户端调用时仍然使用new关键字实现“低层组件”并注入“高层组件”。

4)第四阶段,“高层组件”包含(Has-A)“低层组件”的抽象父类或接口,接口被提取成DLL接口库中定义。构造方法,接口,或Set属性实现注入,“高层组件”与“低层组件”耦合转到抽象上,解除了耦合。此时客户端调用时仍然使用new关键字实现“低层组件”并注入“高层组件”。

DIP的原则就是要“低层组件”依赖“高层组件”,后来被衍生为“低层组件”和“高层组件”均依赖于抽象。通过以上几个阶段可以看出:

第一阶段——“高层组件”依赖“低层组件”(这时当“低层组件”未完成时,“高层组件”并不能实现,因为“高层组件”强烈依赖“低层组件”)。

第二阶段——虽然有了抽象并依赖抽象,但仍然是“高层组件”依赖“低层组件”。接口由“低层组件”定义,如果“低层组件”未抽象出接口,“高层组件”不能实现,“高层组件”依然依赖“低层组件”。

第三阶段——为了使“低层组件”依赖“高层组件”(此时出现了依赖反转),将接口放在了“高层组件”,也就是说“高层组件”定义了接口,而“低层组件”去实现。此时可以先设计“高层组件”然后实现“低层组件”。此时实现了“高层组件”和“低层组件”的解耦。

第四阶段——DIP在第三阶段已经完成,然而人们确发现,在客户端(掉用“高层组件”和“低层组件”的程序集)总得new出一个“低层组件”来注入“高层组件”,这样客户端中出现的耦合,能不能把这种耦合也消除了那?“配置文件”+“反射”的IOC框架实现了这个愿望,在“配置文件”里实现了“接口”和“实现”的映射,不用再用new来实例化并注入了,这样也就出现了所谓的IOC框架。

3. IOC:控制反转(Inversion of Control),其实就是DIP的实现。此时客户端仍然在使用new关键字实 现“低层组件”并注入“高层组件”,虽然“高层组件”和“低层组件”解耦了,但是在客户端那里确依然存在耦合,这时以“配置文件”+“反射”的IOC框架 出现了,在代码中找不到new关键字,因为接口对应的实现已经在配置文件中,IOC框架将接口和实现进行了自动装配(这个时候IOC解耦的强大就很容易看 出来了,如果我是架构师,那么此时我只需要将“接口”DLL和“高层组件设计出来”即可,不需要有任何的“低层组件”实现,然后我把“接口”DLL扔给程 序员,让他们去实现“接口”,等程序员完成后,我不用修改任何代码,只需要把实现“接口”的DLL放入执行目录,并将IOC配置文件中的“接口”和程序员实现的DLL进行一下Mapping即可)。

解耦,是一门很迷人的艺术,简介到此,下面开始正题。

基础:

一、有一个(Has-A),是一个(Is-A)区别:

继承是面向对象的基础,但是如果继承的层次太深使得逻辑复杂不好掌控。

有很多OO大神都提倡多用“组成”(Has-A)少用“继承”(Is-A),我感觉还是有一定道理的,当时少用并不代表不用,个人感觉父类为抽象类或接口最为有用,有一些设计模式就是靠继承来达到优美的Code的,如模板方法,策略模式等等......

二、面向接口编程(面向抽象编程):

此处的接口并不是说Interface,而是指抽象,包括Interface和abstract class。

1. 传统菜鸟Code

public class IntelCPU
{
//...
}

IntelCPU intelCPU = new IntelCPU(); //传统Code

2. 面向接口编程

public abstract class CPU
{
//...
} public class IntelCPU : CPU
{
//...
} public class AMDCPU : CPU
{
//...
}
复制代码

CPU cpu = new IntelCPU(); //面向接口编程

CPU cpu = new AMDCPU(); //面向接口编程

此时CPU是个abstract class,而IntelCPU和AMDCPU都是它的子类。

三、解耦的发展史:

面向对象->面向接口->配置文件动态装配

相当于:

依赖具体类->依赖抽象->依赖抽象+配置文件动态组装“抽象”和“实现”

深入:

1. DI(Dependency Injection)依赖注入

个人认为所谓的依赖注入,可拆分成“依赖”和“注入”。

依赖:

对于组件A和组件B,A组件中包含B组件,这时就是Has-A的关系,也就是说A组件和B组件之间存在“依赖”。

注入:

此时讨论的均是B组件不是系统自定义的“元数据类型”的情况,即B组件不做为局部变量使用。(元数据类型:string,int,double 等等都属于元数据,与其他类不产生依赖关系)

1)对于非面向接口编程(也就是说B不存在抽象父类):

一般会在A组件的构造方法中初始化B组件的一个实例,此时并未从外界注入。

public class A
{
private B _b; public A()
{
_b = new B();
}
} public class B
{
}
复制代码

2)对于面向接口编程(也就是说B存在抽象父类)

  • 构造函数注入:在A组件的构造方法中初始化B组件的一个实例,此时是由参数从类外将B组件实例传入。

    public class A
    {
    private Base _base; public A(Base base)
    {
    _base = base;
    }
    } public class Base
    {
    } public class B : Base
    {
    }
    复制代码
  • Set属性(Property)注入:在A组件中public出一个属性用来从类外将B组件实例传入。
    public class A
    {
    private Base _base; public Base BaseInstance
    {
    set{ _base = value;}
    }
    } public class Base
    {
    } public class B : Base
    {
    }
    复制代码
  • 接口注入:定义接口方法实现注入,与构造方法注入相似。
    public interface IDependencyInjection
    {
    void Injection(Base base);
    } public class A : IDependencyInjection
    {
    private Base _base; public void Injection(Base base)
    {
    _base = base;
    }
    } public class Base
    {
    } public class B : Base
    {
    } 复制代码
  • 配置文件加反射注入(各种IOC容器框架):通过配置文件中的Interface或abstract class名和实现的类名,利用反射将Interface或abstract class与其实现实现动态装配完成注入。(详细解释见Demo分析)

    Demo分析(☆☆☆重点☆☆☆):

    测试代码使用了NUnit,Demo中虚拟了一个场景,高层组件是“电脑”,低层组件是“中央处理器”和“显卡”。现在让我们解耦吧,Start~~~

    一、依赖注入

    使用控制台输出,为了更直观的显示,使用了AOP,利用Castle中的Proxy实现,Proxy即代理,是一种模式,当你操作 Proxy好像操作实际对象一样,但是在Proxy内同时进行了其他操作(代理分为安全代理,远程代理等等,有兴趣可以研究下Proxy模式)。以下为 AOP实现代码:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using System.Configuration;
    using Castle.DynamicProxy;
    using Landpy.AOP; namespace AOP
    {
    class AOPHelper
    {
    static 拦截器 interceptor = new 拦截器();
    static ProxyGenerator generator = new ProxyGenerator(); public static T GetProxyInstance<T>(params object[] constructorParmas) where T : IAOPInterface
    {
    return generator.CreateClassProxy(typeof(T), interceptor, constructorParmas) as T;
    }
    }
    } 复制代码

    其中“拦截器”类继承了StandardInterceptor接口,用来实现对代码的AOP,AOP(面向切面变成)把关注点放在了某个切面上,比如处理异常、记录日志和处理事务等等。本程序使用AOP来监视Method执行的开始和执行的结束。

    ProxyGenerator类(Castle.DynamicProxy命名空间中)利用type、对应的实现的拦截器(本Code中是“拦截器”类实例interceptor)以及被代理对象构造方法的参数,获得对应的代理对象。

    方法GetProxyInstance用来获得T类型的代理对象,为了保证T的正确性对T做了泛型约束,只有继承IAOPInterface的类型 才可以做为泛型类型(IAOPInterface接口没有任何实现,只是为了可以得到一个约束,这里是个小技巧,防止不兼容的泛型类型被传入)。

    IAOPInterface接口代码如下:

    using System;
    using System.Collections.Generic;
    using System.Text; namespace Landpy.AOP
    {
    public class IAOPInterface
    {
    }
    }

    拦截器类代码如下:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Castle.DynamicProxy; namespace AOP
    {
    class 拦截器 : StandardInterceptor
    {
    protected override void PreProceed(
    IInvocation invocation, params object[] args)
    {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("=================【{0}】类【{1}】方法 开始================="
    , invocation.Method.ReflectedType.Name, invocation.Method.Name);
    Console.ForegroundColor = ConsoleColor.White;
    base.PreProceed(invocation, args);
    } public override object Intercept(IInvocation invocation, params object[] args)
    {
    return base.Intercept(invocation, args);
    } protected override void PostProceed(IInvocation invocation, ref object returnValue, params object[] args)
    {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("=================【{0}】类【{1}】方法 结束================="
    , invocation.Method.ReflectedType.Name, invocation.Method.Name);
    Console.ForegroundColor = ConsoleColor.White;
    base.PostProceed(invocation, ref returnValue, args);
    }
    }
    }

    可以看到“拦截器”类继承了StandardInterceptor接口,override了StandardInterceptor接口的三个方法:

    1. PreProceed代表在Metod执行之前。

    2. Intercept代表在Mehod劫持的过程中。

    3. PostProceed代表在Method执行之后。

    StandardInterceptor接口继承IInterceptor接口,通过以下代码可以看到IInterceptor接口只有 Intercept方法(简单的处理劫持),而StandardInterceptor接口扩展了IInterceptor接口加入了 PreProceed方法和PostProceed方法,这样我们就能出来Mehod调用前和调用后了。两个接口定义如下,有兴趣可以看下,与本文关系不 大,只是顺带简单的描述了下AOP。

    using System;
    
    namespace Castle.DynamicProxy
    {
    [Serializable]
    public class StandardInterceptor : IInterceptor
    {
    public StandardInterceptor(); public virtual object Intercept(IInvocation invocation, params object[] args);
    protected virtual void PostProceed(IInvocation invocation, ref object returnValue, params object[] args);
    protected virtual void PreProceed(IInvocation invocation, params object[] args);
    }
    }
    using System;
    
    namespace Castle.DynamicProxy
    {
    public interface IInterceptor
    {
    object Intercept(IInvocation invocation, params object[] args);
    }
    }

    1) 构造方法注入:

    上类图

    由类图可以看出,采用了面向“接口”编程,“接口”为“中央处理器”,“显卡”和“电脑”,“电脑”是高级组件其中含有一个“中央处理器”(抽象“接口”)和一个“显卡”(抽象“接口”)。

    为了使组件间的耦合降低,采用了构造方法注入(“电脑”组件内的构造函数包含两个参数,分别为“中央处理器”和“显卡”,这样即使联想 集团为了降低成本将中央处理器由“IntelCPU”变为了“AMDCPU”也不需要去修改高层组件“电脑”的代码了,只需要在客户端程序集中将注入的 “中央处理器”类型更改为“AMDCPU”即可)。详细代码见完整Demo,注入代码如下:

    public 电脑(中央处理器 cpu, 显卡 gpu)
    {
    _cpu = cpu;
    _gpu = gpu;
    }

    2)接口注入:

    上类图

    由类图可以看出,接口注入与构造函数注入十分相似,接口注入也有更多的好处,比如更加降低耦合性,把耦合由构造方法转移到了抽象接口。“I接口注入”接口定义了注入方法void BuildComputer(中央处理器 cpu, 显卡 gpu)。

    详细代码见完整Demo,注入代码如下:

    #region I接口注入 成员
    
    public void BuildComputer(中央处理器 cpu, 显卡 gpu)
    {
    _cpu = cpu;
    _gpu = gpu;
    } #endregion

    3)Set属性注入:

    上类图

    属性(Property)是C#的语法糖,来源于C的get函数和set函数。属性注入有点像是桥接模式,把对应的“低层组件”直接赋值给“高层组件”对象,实现了“低层组件”注入到高层组件中。注入代码如下:

    public 中央处理器 CPU
    {
    set { _cpu = value; }
    get { return _cpu; }
    } public 显卡 GPU
    {
    set { _gpu = value; }
    get { return _gpu; }
    }

    至此,DI代码展示完毕,下面是客户端程序集调用代码(使用了NUnit测试框架):

    using System;
    using System.Collections.Generic;
    using System.Text;
    using NUnit.Framework;
    using AOP; namespace ArchitectTestWithNUnit
    {
    [TestFixture]
    public class DependenceInjection_依赖注入
    {
    [TestCase]
    public void 构造函数依赖注入测试()
    {
    Landpy.Computer.ConstructorDI.显卡 gpu = new Landpy.Computer.ConstructorDI.Navidia显卡();
    Landpy.Computer.ConstructorDI.中央处理器 cpu = new Landpy.Computer.ConstructorDI.Intel中央处理器();
    Landpy.Computer.ConstructorDI.电脑 computer = AOPHelper.GetProxyInstance<Landpy.Computer.ConstructorDI.联想电脑>(cpu, gpu);
    Assert.AreEqual("Navidia显卡", computer.GPU.Name);
    Assert.AreEqual("Intel中央处理器", computer.CPU.Name);
    computer.Intruduce();
    } [TestCase]
    public void 属性依赖注入测试()
    {
    Landpy.Computer.PropertyDI.显卡 gpu = new Landpy.Computer.PropertyDI.ATI显卡();
    Landpy.Computer.PropertyDI.中央处理器 cpu = new Landpy.Computer.PropertyDI.AMD中央处理器();
    Landpy.Computer.PropertyDI.电脑 computer = AOPHelper.GetProxyInstance<Landpy.Computer.PropertyDI.联想电脑>();
    computer.CPU = cpu;
    computer.GPU = gpu;
    Assert.AreEqual("ATI显卡", computer.GPU.Name);
    Assert.AreEqual("AMD中央处理器", computer.CPU.Name);
    computer.Intruduce();
    } [TestCase]
    public void 接口依赖注入测试()
    {
    Landpy.Computer.InterfaceDI.显卡 gpu = new Landpy.Computer.InterfaceDI.Navidia显卡();
    Landpy.Computer.InterfaceDI.中央处理器 cpu = new Landpy.Computer.InterfaceDI.Intel中央处理器();
    Landpy.Computer.InterfaceDI.电脑 computer = AOPHelper.GetProxyInstance<Landpy.Computer.InterfaceDI.联想电脑>();
    computer.BuildComputer(cpu, gpu);
    Assert.AreEqual("Navidia显卡", computer.GPU.Name);
    Assert.AreEqual("Intel中央处理器", computer.CPU.Name);
    computer.Intruduce();
    }
    }
    }

    对于Code的演化,同样使用了AOP,只是此时生成的代理对象是通过App.config文件配置得到的,代码如下:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using System.Configuration;
    using Castle.DynamicProxy; namespace ArchitectTest
    {
    class AOPHelper
    {
    static 拦截器 interceptor = new 拦截器();
    static ProxyGenerator generator = new ProxyGenerator(); public static object GetProxyInstance()
    {
    Type type = Type.GetType(ConfigurationManager.AppSettings[CommonParam.配置_阶段测试]);
    return generator.CreateClassProxy(type, interceptor);
    }
    }
    }

    配置文件如下:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <appSettings>
    <add key="阶段测试" value="ArchitectTest.第二阶段测试"/>
    </appSettings>
    </configuration>

    Demo演示了程序员编码思路的变化:

    第一阶段(没抽象接口,没有依赖注入):

    先上“高层组件”(电脑)和“低层组件”(显卡,中央处理器)类图

    1)首先是电脑DLL库类图:

    2)然后是配件(显卡,中央处理器)DLL库类图:

    通过类图可以看到,此时的“高层组件”(电脑DLL)和“低层组件”(配件DLL)均没有依赖于抽象。

    所以在“高层组件”(电脑DLL)中,包含对“低层组件”(配件DLL)的强烈依赖[“高层组件”(惠普电脑)中死死的绑定了“低层组件”(AMD中央处理器,ATI显卡),此时如果惠普公司想要将“AMD中央处理器”换为“Intel中央处理器”,那么就需要对“高层组件”(惠普电脑)进行修改。同样的现象也存在于“联想电脑”中。],这里存在我们不期望的耦合。

    此时的依赖关系是:惠普电脑->AMD中央处理器+ATI显卡。

    以下是客户端程序集调用代码:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Landpy.Computer.OneStep; namespace ArchitectTest
    {
    public class 第一阶段测试 : 阶段测试
    {
    public virtual void 测试()
    {
    //没有应用抽象,依赖注入,通过引用的程序集可以看出耦合性很高
    //高层组件(联想电脑)不能动态更换低层组件(CPU,GPU)
    联想电脑 lenovo = new 联想电脑();
    lenovo.Introduce();
    Console.WriteLine("------------------传说中的分割线------------------");
    惠普电脑 hp = new 惠普电脑();
    hp.Introduce();
    }
    }
    }

    第二阶段(加入抽象接口到低层组件,高层组件依赖注入低层组件抽象)

    先上“高层组件”(电脑)和“低层组件”(显卡,中央处理器)类图

    1)首先是电脑DLL库类图:

    2)然后是配件(显卡,中央处理器)DLL库类图:

    在第二阶段加入“抽象接口”(显卡,中央处理器)到“低层组件”(配件DLL),“高层组件”(电脑DLL)被“低层组件”抽象(配件DLL中的“抽象接口”)依赖注入。

    程序员有个习惯,那就是编写“低层组件”并在“低层组件”中定义接口,然后用“高层组 件”去调用。通俗来讲就是名字为B的DLL中定义了“抽象接口”和实现,名字为A的DLL根据这些接口去调用,这也是大部分程序的思路,然而这样必然导致 “高层组件”依赖“低层组件”,如果“低层组件”没有完成,“高层组件”无法编写。实际上这是一种很奇怪的现象,应该是“高层组件”拥有更高的决定权,由 “高层组件”定义接口,“高层组件”完成后,再用“低层组件”去实现“高层组件”定义的接口(这个是第三阶段出现的原因)。

    此时的依赖关系是:电脑->配件。

    以下是客户端程序集调用代码:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Landpy.Computer.TwoStep; namespace ArchitectTest
    {
    public class 第二阶段测试 : 阶段测试
    {
    public virtual void 测试()
    {
    //高层组件(电脑)依赖低层组件(中央处理器,显卡),只有低层组件完成后高层组件才能编写
    //所以此时需要进行依赖倒置,也就是演化为遵循DIP的代码(见第三阶段测试)。
    电脑 lenovo = new 联想电脑();
    lenovo.Introduce();
    Console.WriteLine("------------------传说中的分割线------------------");
    电脑 hp = new 惠普电脑();
    hp.Introduce();
    //动态修改联想配件,高层组件(电脑)不再依赖低层组件(配件),依靠面向接口编程+依赖注入完成了解耦。
    Console.WriteLine("------------------传说中的分割线------------------");
    lenovo = new 联想电脑(new Landpy.Fitting.TwoStep.AMD中央处理器(), new Landpy.Fitting.TwoStep.ATI显卡());
    lenovo.Introduce();
    }
    }
    }

    第三阶段(加入抽象接口到高层组件实现依赖倒置)

    先上“高层组件”(电脑)和“低层组件”(显卡,中央处理器)类图

    1)首先是电脑DLL库类图:

    2)然后是配件(显卡,中央处理器)DLL库类图:

    加入“抽象接口”(电脑,显卡,中央处理器)到高层组件实现依赖倒置,此时“低层组件”(配件DLL)依赖“高层组件”(电脑DLL),出现了依赖反转现象(DIP),然而对于“低层组件”(配件DLL)来说“高层组件”(电脑DLL)中的内容它不是全部需要,“低层组件”(配件DLL)只需要“高层组件”(电脑DLL)中的“抽象接口”,如果此时“低层组件”(配件DLL)引入“高层组件”(电脑DLL)会有一部分“高层组件”(电脑DLL)编码浪费。所以需要将“抽象接口”(电脑,显卡,中央处理器)独立为一个单独的程序集(DLL)(这个是第四阶段出现的原因)。

    这个阶段用到了IOC框架,实现了“抽象接口”和实现的动态组装。

    此时的依赖关系是:电脑<-配件。

    以下是客户端程序集调用代码:

    IOCHelper文件:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Castle.Windsor; namespace ArchitectTest
    {
    class IOCHelper
    {
    static IWindsorContainer windorContainer = null; public static T GetService<T>(string xmlConfigFileName)
    {
    windorContainer = new WindsorContainer(xmlConfigFileName);
    return windorContainer.GetService<T>();
    }
    }
    }

    测试代码:

     using System;
    using System.Collections.Generic;
    using System.Text;
    using Landpy.Computer.ThreeStep; namespace ArchitectTest
    {
    public class 第三阶段测试 : 阶段测试
    {
    #region 阶段测试 成员 public virtual void 测试()
    {
    //依赖倒置实现,并由IOC装配,低层组件(中央处理器,显卡)依赖高层组件(电脑),可以先设计高层组件接口,
    //设计框架完成后,再对接口进行实现,可以实现架构师的提前设计。
    //此时低层组件依赖整个高层组件,但低层组件并不需要所有的高层组件信息,所以可以将接口部分抽取出来成为
    //单独的库,再用IOC进行装配(见第四阶段)。
    //☆☆☆注意:将生成的高层组件和低层组件都放到运行目录下☆☆☆△
    电脑 lenovo = IOCHelper.GetService<电脑>("StepThreeComponentsSetting.xml");
    lenovo.Introduce();
    } #endregion
    }
    }

    配置文件:

    配置
    
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <components>
    <component id="Computer" service="Landpy.Computer.ThreeStep.电脑,Landpy.Computer.ThreeStep"
    type="Landpy.Computer.ThreeStep.联想电脑,Landpy.Computer.ThreeStep" />
    <component id="GPU" service="Landpy.Computer.ThreeStep.显卡,Landpy.Computer.ThreeStep"
    type="Landpy.Fitting.ThreeStep.ATI显卡,Landpy.Fitting.ThreeStep" />
    <component id="CPU" service="Landpy.Computer.ThreeStep.中央处理器,Landpy.Computer.ThreeStep"
    type="Landpy.Fitting.ThreeStep.AMD中央处理器,Landpy.Fitting.ThreeStep" />
    </components>
    </configuration>

    第四阶段(将抽象接口抽取成单独库,防止过度引用)

    先上“高层组件”(电脑)和“低层组件”(显卡,中央处理器)类图

    1)首先是系统接口DLL库类图:

    2)然后是电脑DLL库类图:

    3)然后是配件(显卡,中央处理器)DLL库类图:

    此时依赖倒置更进一步,完成了依赖抽象。“高层组件”(电脑DLL)和“低层组件”(配件DLL)均依赖“抽象接口”(电脑,显卡,中央处理器),此时的开发过程可以更换为以下步骤:

    1)设计并生成“抽象接口”(电脑,显卡,中央处理器)DLL。

    2)公布“抽象接口”DLL(电脑,显卡,中央处理器)供“高层组件”(电脑DLL)和“低层组件”(配件DLL)引用。

    此时“高层组件”(电脑DLL)和“低层组件”(配件DLL)可以并行开发。

    3)“高层组件”和“低层组件”集成。

    我个人猜想,如果是系统搭建,架构师可以先设计好接口,然后把接口包括文档公布给程序员,程序员完成具体功能的实现,架构师负责系统设计和组装的工作,使得系统的扩展性得到保障,尽量满足OCP。

    此时的依赖关系是:电脑->系统接口;配件->系统接口。

    以下是客户端程序集调用代码:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Landpy.SystemInterface.FourStep; namespace ArchitectTest
    {
    public class 第四阶段测试 : 阶段测试
    {
    #region 阶段测试 成员 public virtual void 测试()
    {
    //最终设计
    //☆☆☆注意:将生成的高层组件和低层组件都放到运行目录下☆☆☆△
    电脑 lenovo = IOCHelper.GetService<电脑>("StepFourComponentsSetting.xml");
    lenovo.Introduce();
    } #endregion
    }
    }

    配置文件:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <components>
    <component id="Computer" service="Landpy.SystemInterface.FourStep.电脑,Landpy.SystemInterface.FourStep"
    type="Landpy.Computer.FourStep.惠普电脑,Landpy.Computer.FourStep" />
    <component id="GPU" service="Landpy.SystemInterface.FourStep.显卡,Landpy.SystemInterface.FourStep"
    type="Landpy.Fitting.FourStep.Navidia显卡,Landpy.Fitting.FourStep" />
    <component id="CPU" service="Landpy.SystemInterface.FourStep.中央处理器,Landpy.SystemInterface.FourStep"
    type="Landpy.Fitting.FourStep.Intel中央处理器,Landpy.Fitting.FourStep" />
    </components>
    </configuration>

    职责原则(SRP),开放封闭原则(OCP),LisKov替换原则 (LSP)依赖倒置原则(DIP),接口隔离原则(ISP)以及各种设计模式(原来写过Dota版设计模式,现在又有了新的体会,也了解了新的模式MVP 与MVC、Transation Script、Table Module、Active Record[我可爱的Castle]),什么DDD(领域驱动开发),TDD(测试驱动开发)开发模式,AOP,SOA,DTO等等
    http://www.cnblogs.com/iwangjun/archive/2012/03/11/2390348.html

OOP、DI、IOC的关系的更多相关文章

  1. 什么是AOP和OOP,IOC和DI有什么不同?

    什么是AOP和OOP,IOC和DI有什么不同? 解答: 1)面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构.AOP是OOP的延续, ...

  2. 对于Spring中AOP,DI,IoC概念的理解

    IOC IoC(inversion of Control),控制反转.就好像敏捷开发和SCRUM一样,不是什么技术,而是一种方法论,一种工程化的思想.使用IoC的思想意味着你将设计好的对象交给容器控制 ...

  3. Spring专题2: DI,IOC 控制反转和依赖注入

    合集目录 Spring专题2: DI,IOC 控制反转和依赖注入 https://docs.spring.io/spring/docs/2.5.x/reference/aop.html https:/ ...

  4. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  5. Helloworld之Spring依赖注入/控制反转(DI/IoC)版

    Helloworld之Spring依赖注入/控制反转(DI/IoC)版 作者:雨水, 日期:2014-10-29 摘要:本文主要用于培训刚開始学习的人理解Spring中的依赖注入的基本概念. 先介绍依 ...

  6. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

  7. ASP.NET MVC不可或缺的部分——DI(IOC)容器及控制器重构的剖析

    ASP.NET MVC不可或缺的部分——DI(IOC)容器及控制器重构的剖析   IoC框架最本质的东西:反射或者EMIT来实例化对象.然后我们可以加上缓存,或者一些策略来控制对象的生命周期,比如是否 ...

  8. Ioc和DI之间的关系(依赖注入的核心概念)

    1.开篇闲话 由于之前做的很多项目都没接触到这个,后来到了另一个公司,他们的代码结构是基于领域驱动设计的,其中里面的对象都是通过依赖注入方式(Sprint.NET)实现的,也大致了解了哈,在网上搜了些 ...

  9. sping IOC和DI 初始化和关系

    springIOC和spring DI作为spring core的核心思想,有必要学习下才能更好的使用spring ========================================== ...

随机推荐

  1. 渗透测试平台Vulnreport介绍与使用

    渗透测试平台Vulnreport介绍与使用   在这篇文章中,我们将跟大家讨论一些关于渗透测试方面的内容,并给大家介绍一款名叫Vulnreport的新型开源工具,而这款工具将能够让任何场景下的渗透测试 ...

  2. 前端入门Js笔记

    T 001 ____________--信息页面展示 需求分析: 有一个页面,在页面上有很多文字信息,且格式不一. 技术分析: html: 文字标签: 字体标签: 标题标签: 其他标签: 排版标签: ...

  3. UDS报文解读

    UDS(Unified Diagnostic Services,统一的诊断服务)诊断协议是ISO 15765 和ISO 14229 定义的一种汽车通用诊断协议,位于OSI模型中的应用层,它可在不同的汽 ...

  4. [ZOJ 3063] Draw Something Cheat

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4706 思路:字符串是一个集合(由0到多个A~Z字符组成),我们可以假 ...

  5. CF #546 D.E

    D coun[i]表示[i]这个数右边有多少个数j能和他组成题中所给的二元组(i,j) 如果一个数的coun[i]=n-i-ans 那么说明他可以与最后一个交换 同时不计算贡献 因为它是向右走的 对左 ...

  6. linux 计算机概论 Linux介绍

    CPU: CPU内部可以分为两个主要单元:算数逻辑单元和控制单元. 算数逻辑单元主要用于程序运算和逻辑判断,控制单元主要用于协调各个组件和各单元的工作. CPU基本可以分为两种: 精简指令集和复杂指令 ...

  7. Dom修改元素样式

    提纲:我们可以通过js来修改元素的大小,颜色,位置等样式 1.element.style                         行内样式的操作 2.element.className    ...

  8. [Ahoi2009]self 同类分布

    1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec  Memory Limit: 64 MBSubmit: 2357  Solved: 1079[Submit][ ...

  9. python的序列化模块

    最近机器学习的模型需要序列化和反序列化,因为写个博客总结一下几个模型和数据等序列化的模块.

  10. hive 汇率拉链表转日连续流水表

    1.什么是拉链表 拉链表是针对数据仓库设计中表存储数据的方式而定义的,顾名思义,所谓拉链,就是记录历史.记录一个事物从开始,一直到当前状态的所有变化的信息. 我们先看一个示例,这就是一张拉链表,存储的 ...