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

1,关于IOC模式

先看一些名词含义:

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

DI: Dependency Injection 依赖注入,简称

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

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

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

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

    [.net 面向对象程序设计深入](26)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1) 1,关于IOC模式 先看一些名词含义: IOC: Inversion of con ...

  2. C#设计模式之控制反转即依赖注入-微软提供的Unity

    使用VS2015的Nuget管理器下载Unity. 程序员接口类: 1 namespace UnityDemo 2 { 3 public interface IProgrammer 4 { 5 voi ...

  3. C#设计模式之控制反转即依赖注入-Spring.NET

    主流的依赖注入方案:微软企业库中的Unity.Spring.NET.StructureMap.Ninject.Castle Windsor等等. 本章用简单的案例讲解 Spring.NET IOC-控 ...

  4. 小菜学习设计模式(五)—控制反转(Ioc)

    写在前面 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Metho ...

  5. Ioc 器管理的应用程序设计,前奏:容器属于哪里? 控制容器的反转和依赖注入模式

    Ioc 器管理的应用程序设计,前奏:容器属于哪里?   我将讨论一些我认为应该应用于“容器管理”应用程序设计的原则. 模式1:服务字典 字典或关联数组是我们在软件工程中学到的第一个构造. 很容易看到使 ...

  6. 【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入

    系列目录 上|理论基础+实战控制台程序实现AutoFac注入 下|详解AutoFac+实战Mvc.Api以及.NET Core的依赖注入 前言 本来计划是五篇文章的,每章发个半小时随便翻翻就能懂,但是 ...

  7. ASP.NET设计模式(一)、适配器模式、依赖注入依赖倒置、空对象模式

    鸟随凤鸾,人伴贤良,得以共之,我之幸也.说的是鸟随着鸾凤可以飞的更高远,人和比自己境界高的相处,自己也会得到熏染进步. 一.概述 分享出来简单的心得,望探讨 依赖倒置 依赖注入 Adapter模式 N ...

  8. Angular4.0从入门到实战打造在线竞拍网站学习笔记之三--依赖注入

    Angular4.0基础知识之组件 Angular4.0基础知识之路由 依赖注入(Dependency Injection) 正常情况下,我们写的代码应该是这样子的: let product = ne ...

  9. [.net 面向对象程序设计深入](0) 开篇

    [.net 面向对象程序设计深入](0)开篇        [.net 面向对象编程基础]和 [.net 面向对象程序设计进阶]在15年底写完了,群里也加进来不少热爱学习的小伙伴.让我深切感受到在这个 ...

随机推荐

  1. css3基本属性

    一.css属性:1.层叠性:当出现相同的选择器时,属性冲突的时候,后面的属性会把前面的属性 覆盖掉. 2.继承:当存在父子关系的时候,子元素会继承父元素的部分属性 注意: a标签不会继承颜色:h标签不 ...

  2. .net基础学java系列(六)Java基础

    一.废话 .net学java为何一直没入坑?其实大家都知道,语法很相似,就是使用的习惯不同 稍微的语法差异 结构体系不同 IDE不同 类库集不同 各种框架不同 对于我来说,我一直被第三道坎拦住了,所以 ...

  3. No enclosing instance of type Test is accessible. Must qualify the allocation with an enclosing instance of type Test (e.g. x.new A() where x is an instance of Test).

    Java编写代码过程中遇到了一个问题,main方法中创建内部类的实例时,编译阶段出现错误,查看错误描述: No enclosing instance of type Test is accessibl ...

  4. Mybatis 源码学习系列

    前言 很久以前,我们学习了Java,从一个控制台的 Hello world .开始,我们进入了面向对象的世界. 然后由学习了SQL语言,可以写出SQL语句来将尘封在硬盘之下的数据库数据,展现出来. 后 ...

  5. org.springframework.web.context.ContextLoaderListener 解决方案

    tomcat启动项目报错,没找到这个类 我直接下了一个spring-web-4.3.8.RELEASE.jar 的 jar 包方到web-inf目录下.问题解决. 补充: 如果在检查了项目 jar 环 ...

  6. UOJ#206. 【APIO2016】Gap 构造 交互题

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ206.html 题解 T = 1 的情况直接大力从两边向中间询问即可. T = 2 的情况挺妙的,我没想到. 考虑首先花费 ...

  7. python 字典、列表、字符串 之间的转换

    1.列表与字符串转换 1)列表转字符串: 将列表中的内容拼接成一个字符串 将列表中的值转成字符串 2)字符串转列表: 用eval转换 将字符串每个字符转成列表中的值 将字符串按分割成列表 2.列表与字 ...

  8. Kafka提交offset机制

    在kafka的消费者中,有一个非常关键的机制,那就是offset机制.它使得Kafka在消费的过程中即使挂了或者引发再均衡问题重新分配Partation,当下次重新恢复消费时仍然可以知道从哪里开始消费 ...

  9. 实验二《Java面向对象程序设计》实验报告

    一.实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 二.实验步骤 (一)单元测试 1.三种代码:伪代码.测 ...

  10. vue小技巧之偷懒的文件路径——减少不必要的代码

    众所周知,我们写vue项目的时候都会创建很多个文件,尤其是一些中大型项目,会有很深的文件夹,当你去引入的时候,要写很长的路径比如我要引入一个css文件, 必须得 import  '../../../s ...