[.net 面向对象程序设计深刻](31)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)

[.net 面向对象程序设计深刻](31)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1)
html

1,关于IOC模式 

先看一些名词含义:git

IOC: Inversion of control 控制反转,简称github

DI: Dependency Injection 依赖注入,简称spring

DIP: 依赖倒置原则 一种软件架构设计的原则(抽象概念),“设计模式使用场景及原则”一篇中介绍过设计模式的几种原则之一。数据库

IoC容器:依赖注入的框架,用来映射依赖,管理对象建立和生存周期(DI框架)。编程

(1)IOC和DI,是站在不一样角度的描述,自己是同一个概念设计模式

先说说这两个对于初次接触到的同窗难以理解的概念,首先这两个东东是一回事,只是由于角度不一样,有了两个名字。浏览器

举个不太恰当的例子,好比一我的,爸爸叫你儿子,爷爷叫你孙子,那这个儿子和孙子都是你,是同一我的,只是站的角度不一样,这么说容易理解了吧。架构

依赖注入(DI)是从应用程序的角度在描述,能够把依赖注入描述完整点:应用程序依赖容器建立并注入它所须要的外部资源;框架

而控制反转(IOC)是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所须要的外部资源。

(2)是一种大粒度的设计模式

和GOF的23种设计模式相比较。IOC模式(一般中文称"依赖注入"或“依赖倒置”),因为出现时间比较晚,没有被收录。

其次和23种设计模式相比较,它的粒度更大一些,和MVC模式同样,一般到架构层面了,而不是具体代码片断级别。理解到这里就能够了。

但它仍然是设计模式,只是和23种设计模式比,23种模式是战术层面,IOC模式是战略层面的。 

依赖注入映射到面向对象程序开发中就是:高层类应该依赖底层基础设施来提供必要的服务。

编写松耦合的代码提及来很简单,可是实际上写着写着就变成了紧耦合。

一个比较难理解的正式定义以下:

依赖注入(Dependency Injection),是这样一个过程:因为某客户类只依赖于服务类的一个接口,而不依赖于具体服务类,因此客户类只定义一个注入点。

在程序运行过程当中,客户类不直接实例化具体服务类实例,而是客户类的运行上下文环境或专门组件负责实例化服务类,而后将其注入到客户类中,保证客户类的正常运行。

下面的说明比较容易理解: 

     理解DI的关键是:“谁依赖谁,为何须要依赖,谁注入谁,注入了什么”,那咱们来深刻分析一下: 

  ●谁依赖于谁:固然是应用程序依赖于IoC容器; 

  ●为何须要依赖:应用程序须要IoC容器来提供对象须要的外部资源; 

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象; 

  ●注入了什么:就是注入某个对象所须要的外部资源(包括对象、资源、常量数据)。 

  IoC和DI由什么关系呢?其实它们是同一个概念的不一样角度描述,因为控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),

     因此2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。 

2,依赖注入的做用

依赖注入不是目的,它是一系列工具和手段。

最终的目的是帮助咱们开发出松散耦合(loose coupled)、可维护、可测试的代码和程序。

这条原则的作法是你们熟知的面向接口,或者说是面向抽象编程。 

常见的三层架构中,虽然表面上表现、业务、数据分层设计。但在数据库层每每会产生一些与具体业务有关的类,并且若是不严格遵循代码规范,会致使产生表现层直接new数据层的状况。

若是要换个数据源呢?假如不使用ADO.NET等试,改成Http呢?这将是领域逻辑层和表现层与之耦合的代码要进行大量更动。 

这样使得整个系统紧耦合,而且可测试性差。

在系统设计过程当中,各个类从上层到下层类之间必然会产生耦合,若是彻底没有耦合,那么这个类或程序集就能够从项目中移除了。

所以如何使之达到松散耦合,从而提升可测试性呢?依赖注入将能很好的解决上述问题。 

3,IOC模式应用示例

下面以一个简单购物过程为例来讲明IOC模式如何实现松散耦合。

(1)传统三层模式

代码以下:

    public class DalSqlServer
    {
        public void Add()
        {
            Console.WriteLine("在数据库中添加一条订单!");
        }
    }
    public class Order
    {
        private readonly DalSqlServer dal = new  DalSqlServer();//添加一个私有变量保存数据库操做的对象
        public void Add()
        {
            dal.Add();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Order order = new Order();
            order.Add();
            Console.Read();

        }
    }

运行结果:

 上面的代码看着功能都实现了,然而忽然老板说,SqlServer要花钱买,咱们使用免费的Access吧,好吧,那就改改喽。改动后以下:

这时,咱们只能再增长一个DalAcess类,来解决,代码以下:

public class DalAccess
    {
        public void Add()
        {
            Console.WriteLine("在Access数据库中添加一条订单!");
        }
    }
   public class Order
    {
        private readonly DalAccess dal = new DalAccess();//添加一个私有变量保存Access数据库操做的对象
        public void Add()
        {
            dal.Add();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Order order = new Order();
            order.Add();
            Console.Read();

        }
    }

运行结果:

正在这时,老板来了,说最近生意好,订单剧增,改为MySql数据库吧,蒙蔽了吧,示例代码中只有一个Add()的方法,可实际项目中,不知道有多少工做量了。

(2)使用IOC模式改进

只因此在需求变化时,咱们的代码改动量如此之大,是由于耦合,耦合,耦合。

前面说了耦合不可能不存在,若是不存在,那这个代码就能够从项目中移除了,可是要让让代码可维护性强,就必须使用模式化的开发

固然依赖注入就能解决上述问题,依赖注入(DI),它提供一种机制,将须要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象

咱们示例中低层模块就是DalSqlServer,DalAccsess,DalMySql等,高层模块就是Order.

那么如何在Order内部不依赖DalSqlServer,DalAccsess,DalMySql的状况下传递呢,

这就须要对DalSqlServer,DalAccsess,DalMySql进行抽象设计,咱们设计一个IData数据接口对象,让DalSqlServer,DalAccsess,DalMySql去具体实现它,

在传递过程当中,咱们只须要订单处理Order和数据接口层IData耦合,这样,即便数据库再变化,但IData是稳定的。也不须要再改动Order中的代码了。是否是很不错的解耦呢?

改进后以下:

代码以下:

   public class DalOracle : IData
    {
        public void Add()
        {
            Console.WriteLine("在Oracle数据库中添加一条订单!");
        }
    }
    public class DalAccess : IData
    {
        public void Add()
        {
            Console.WriteLine("在Access数据库中添加一条订单!");
        }
    }
    public class DalMySql : IData
    {
        public void Add()
        {
            Console.WriteLine("在MySql数据库中添加一条订单!");
        }
    }
    public class DalSqlServer : IData
    {
        public void Add()
        {
            Console.WriteLine("在SqlServer数据库中添加一条订单!");
        }
    }

定单处理类:

  public class Order
    {
        private IData idata; //定义私有变量保存抽象出来的数据接口

        /// <summary>
        /// 经过构造函数注入
        /// </summary>
        /// <param name="iData"></param>
        public Order(IData iData)
        {
            this.idata = iData; //传递依赖
        }

        public void Add()
        {
            idata.Add();
        }
    }

展现:

    class Program
    {
        static void Main(string[] args)
        {
             //定义空订单
             Order order=null;

            //使用SqlServer
            order = new Order(new DalSqlServer());
            order.Add();

            //使用Oracle
            order = new Order(new DalOracle());
            order.Add();

            //使用Accesss
            order = new Order(new DalAccess());
            order.Add();

            //使用MySql
            order = new Order(new DalMySql());
            order.Add();

            Console.Read();
        }
    }

 这样就能够随意切换数据库了,运行结果以下:

(3)更进一步改进,控制反转

上面说到站在另外一个角度讲,咱们把选择数据库的权限交给第三方,是否是能够不用每次在建立订单时都指定依赖对象(即具体数据类),也就是控制反转。

针对上面的每次指定依赖对象的问题,处理的方式不少,最简单的咱们能够经过一个配置文件来指定所使用的具体数据库,在传递时经过反射的方式来映射数据类。

这样就就灵活多了。

4,IOC注入的几种方式

 (1)构造函数注入

上面示例就是这种方式

(2)属性注入

    public class Order
    {
        public IData Idata{get;set;}       

        public void Add()
        {
            this.Idata.Add();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
             //定义空订单
             Order order=null;

            //使用SqlServer
            order = new Order();
            order.Idata = new DalSqlServer();
            order.Add();

            //使用Oracle
            order = new Order();
            order.Idata = new DalOracle();
            order.Add();

            //使用Accesss
            order = new Order();
            order.Idata = new DalAccess();
            order.Add();

            //使用MySql
            order = new Order();
            order.Idata = new DalMySql();
            order.Add();

            Console.Read();
        }
    }

(3)方法注入

 public class Order
    {
        private IData idata; //私有变量保存抽象接口

        //经过Idata方法传递依赖
        public void Idata(IData idata)
        {
            this.idata = idata; 
        }
        public void Add()
        {
            this.idata.Add();
        }
    }
   class Program
    {
        static void Main(string[] args)
        {
             //定义空订单
             Order order=null;

            //使用SqlServer
            order = new Order();
            order.Idata( new DalSqlServer());
            order.Add();

            //使用Oracle
            order = new Order();
            order.Idata(new DalOracle());
            order.Add();

            //使用Accesss
            order = new Order();
            order.Idata(new DalAccess());            
            order.Add();

            //使用MySql
            order = new Order();
            order.Idata(new DalMySql());
            order.Add();

            Console.Read();
        }
    }

5,IOC容器(或DI框架) 

对于大型项目来讲,相互依赖的组件比较多。若是还用手动的方式,本身来建立和注入依赖的话,显然效率很低,并且每每还会出现不可控的场面。正因如此,IoC容器诞生了。IoC容器其实是一个DI框架,它能简化咱们的工做量。它包含如下几个功能: 

动态建立、注入依赖对象。
管理对象生命周期。
映射依赖关系。 

好比比较知名的“基于DDD的现代ASP.NET开发框架--ABP”使用Castle Windsor框架处理依赖注入。它是最成熟的DI框架之一。还有不少其余的框架,如Unity,Ninject,StructureMap,Autofac等等。

下面是园友整理出来的一些经常使用的IOC容器及官网:

(1). Ninject: http://www.ninject.org/

(2). Castle Windsor: http://www.castleproject.org/container/index.html

(3). Autofac: http://code.google.com/p/autofac/

(4). StructureMap: http://docs.structuremap.net/

(5). Unity: http://unity.codeplex.com/

(6). MEF: http://msdn.microsoft.com/zh-cn/library/dd460648.aspx

(7). Spring.NET: http://www.springframework.net/

(8). LightInject: http://www.lightinject.net/ (推荐使用Chrome浏览器访问)

6,总结

(1)依赖注入,是一种结构型的设计模式,即IOC模式。

(2)IOC意思为控制反转和依赖注入是同一律念的不一样角度的说法。

(3)依赖注入是让咱们的应用程序依赖于抽象出来的服务类的接口,而不是具体的服务类,从而在具体的服务类发生需求变化时,咱们注入新的服务接口,作到松散耦合。

(4)依赖注入有三种简单的方式,即构造函数注入,属性注入,方法注入。

(5)在大型项目中为了解决手动建立注入的效率低下,诞生了IOC容器,常见的有:Unity、Ninject、StructureMap、Autofac、Spring.NET等。

7,源代码

https://github.com/yubinfeng/BlogExamples.git

==============================================================================================

返回目录

<若是对你有帮助,记得点一下推荐哦,若有有不明白或错误之处,请多交流>

<对本系列文章阅读有困难的朋友,请先看 《.net 面向对象编程基础》 和 《.net 面向对象程序设计进阶》 >

<转载声明:技术须要共享精神,欢迎转载本博客中的文章,但请注明版权及URL>

.NET 技术交流群:467189533 H.NET 技术交流群

==============================================================================================

相关文章
相关标签/搜索