[.net 面向对象程序设计深入](31)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)
[.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)的更多相关文章
- [.net 面向对象程序设计深入](26)实战设计模式——使用Ioc模式(控制反转或依赖注入)实现松散耦合设计(1)
[.net 面向对象程序设计深入](26)实战设计模式——使用IoC模式(控制反转或依赖注入)实现松散耦合设计(1) 1,关于IOC模式 先看一些名词含义: IOC: Inversion of con ...
- C#设计模式之控制反转即依赖注入-微软提供的Unity
使用VS2015的Nuget管理器下载Unity. 程序员接口类: 1 namespace UnityDemo 2 { 3 public interface IProgrammer 4 { 5 voi ...
- C#设计模式之控制反转即依赖注入-Spring.NET
主流的依赖注入方案:微软企业库中的Unity.Spring.NET.StructureMap.Ninject.Castle Windsor等等. 本章用简单的案例讲解 Spring.NET IOC-控 ...
- 小菜学习设计模式(五)—控制反转(Ioc)
写在前面 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Metho ...
- Ioc 器管理的应用程序设计,前奏:容器属于哪里? 控制容器的反转和依赖注入模式
Ioc 器管理的应用程序设计,前奏:容器属于哪里? 我将讨论一些我认为应该应用于“容器管理”应用程序设计的原则. 模式1:服务字典 字典或关联数组是我们在软件工程中学到的第一个构造. 很容易看到使 ...
- 【半小时大话.net依赖注入】(下)详解AutoFac+实战Mvc、Api以及.NET Core的依赖注入
系列目录 上|理论基础+实战控制台程序实现AutoFac注入 下|详解AutoFac+实战Mvc.Api以及.NET Core的依赖注入 前言 本来计划是五篇文章的,每章发个半小时随便翻翻就能懂,但是 ...
- ASP.NET设计模式(一)、适配器模式、依赖注入依赖倒置、空对象模式
鸟随凤鸾,人伴贤良,得以共之,我之幸也.说的是鸟随着鸾凤可以飞的更高远,人和比自己境界高的相处,自己也会得到熏染进步. 一.概述 分享出来简单的心得,望探讨 依赖倒置 依赖注入 Adapter模式 N ...
- Angular4.0从入门到实战打造在线竞拍网站学习笔记之三--依赖注入
Angular4.0基础知识之组件 Angular4.0基础知识之路由 依赖注入(Dependency Injection) 正常情况下,我们写的代码应该是这样子的: let product = ne ...
- [.net 面向对象程序设计深入](0) 开篇
[.net 面向对象程序设计深入](0)开篇 [.net 面向对象编程基础]和 [.net 面向对象程序设计进阶]在15年底写完了,群里也加进来不少热爱学习的小伙伴.让我深切感受到在这个 ...
随机推荐
- Mysql 多实例 +表损坏
什么是实例? 进程+多个线程+预分配的内存结构 MySQL多实例: 多个进程+多个线程+多个预分配内存结构 多个配置文件: 1)多个端口 2)多个数据目录 3)多个socket文件 ./mysql_i ...
- webpack的三种用法
1.直接命令行使用. 2.node.js API的使用方式. 3.webpack / webpack--config webpack. dev.config.js //根目录创建 webpack.co ...
- MySQL Server 的安装方法及简要步骤
闲扯两句: 小弟不才,由于缺乏明确的职业规划,初毕业的那两年从事的是网络管理工作,接触最多的是计算机硬件和网络设备. 近几年才开始转向DB行业,最初是自学,过程中走了不少弯路,后来参加的专职的DBA培 ...
- P3966 [TJOI2013]单词
P3966 [TJOI2013]单词 题目描述 小张最近在忙毕设,所以一直在读论文.一篇论文是由许多单词组成但小张发现一个单词会在论文中出现很多次,他想知道每个单词分别在论文中出现了多少次. 输入输出 ...
- C程序设计实验报告
试验项目:1.字符与ASCLL码 2.运算符与表达式的运用 3.顺序结构应用程序 4.数学函数的算法描述 5.鸡兔同笼的算法描述 6.确定坐标的算法描述 姓名:郭薪 实验地点:514实验室 试 ...
- HihoCoder - 1038 01背包 动态规划
#1038 : 01背包 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 且说上一周的故事里,小Hi和小Ho费劲心思终于拿到了茫茫多的奖券!而现在,终于到了小Ho领取奖励 ...
- 2018-2019-2 20165328《网络对抗技术》Exp0 Kali安装week1
1.下载Kaili安装资源并解压安装到虚拟机: 2.修改默认字体与共享文件设置: 3.更新软件源与下载中文输入法: 4.安装完成.
- [原创]基于Zynq PS与PL之间寄存器映射 Standalone & Linux 例程
基于Zynq PS与PL之间寄存器映射 Standalone & Linux 例程 待添加完善中
- Docker使用问题记录贴
请参考: https://blog.csdn.net/u013948858/article/details/78429954 问题:安装Docker之后,执行docker run hello-worl ...
- mybatis中有趣的符号#与$
${ }是字符串替换,相当于直接显示数据,#{ }是预编译处理,相当于对数据加上双引号 即#是将传入的值当做字符串的形式,先替换为?号,然后调用PreparedStatement的set方法来赋值,而 ...