My.Ioc 代码示例——避免循环依赖
本文的目的在于通过一些示例,向大家说明 My.Ioc 支持哪些类型的依赖关系。也就是说,如何设计对象不会导致循环依赖。
在 Ioc 世界中,循环依赖是一个顽敌。这不仅因为它会导致 Ioc 容器抛出异常,而且还因为它是不可预知的,尽管通过仔细的配置是可以尽量避免这个问题的。
当我们在 Ioc 容器中注册对象时,我们事先并不知道该对象与其他对象之间的依赖关系,因为依赖关系是由 Ioc 容器管理的。这种依赖关系要等到我们首次调用 container.Resolve(contractType) 时才能确定。而且,它还有可能会随着所依赖对象的注册/注销而发生变化。
因此,要想避免循环依赖问题,那么在设计对象时,仔细地思考该对象与其他对象之间的依赖关系,审慎地区分不同对象的职责就显得比较重要了。当然,对象设计是 Ioc 容器之外的事情了,我们没有办法加以约束。但是,即使您不使用 Ioc 容器,对象设计也是一个值得您深思熟虑的大问题。而一旦您选择使用 Ioc,那也意味着您的对象设计需要更多围绕 Ioc 来考虑。
其实,在 My.Ioc 中要想避免循环依赖也很简单,只要遵循一个原则即可,那就是 [不要在对象构造过程中引入循环依赖]。下面我们结合示例代码来讲解。
using System;
using System.Diagnostics;
using My.Ioc;
using My.Ioc.Exceptions; namespace HowToAvoidCyclicDependency
{
#region Test clazz #region Case 3 public class Grade3
{
private readonly Room3 _room; public Grade3(Room3 room)
{
_room = room;
} public Room3 Room
{
get { return _room; }
}
}
public class Room3
{
public Grade3 Grade { get; set; }
} #endregion #region Case 2 public class Grade2
{
private readonly Room2 _room; public Grade2(Room2 room)
{
_room = room;
room.Grade = this;
} public Room2 Room
{
get { return _room; }
}
}
public class Room2
{
public Grade2 Grade { get; set; }
} #endregion #region Case 1 public class Grade1
{
public Room1 Room { get; set; }
}
public class Room1
{
public Grade1 Grade { get; set; }
} #endregion #endregion class Program
{
static void Main(string[] args)
{
var container = new ObjectContainer(false);
Register(container);
Resolve(container); Console.WriteLine("HowToAvoidCyclicDependency success...");
Console.ReadLine();
} static void Register(IObjectContainer container)
{
container.Register<Grade3>()
.In(Lifetime.Transient());
container.Register<Room3>()
.WithPropertyAutowired("Grade")
.In(Lifetime.Transient()); container.Register<Grade2>()
.In(Lifetime.Transient());
container.Register<Room2>()
.In(Lifetime.Transient()); container.Register<Grade1>()
.WithPropertyAutowired("Room")
.In(Lifetime.Transient());
container.Register<Room1>()
.WithPropertyAutowired("Grade")
.In(Lifetime.Transient()); container.CommitRegistrations();
} static void Resolve(IObjectContainer container)
{
var grade1 = container.Resolve<Grade1>();
Debug.Assert(grade1 == grade1.Room.Grade);
var room1 = container.Resolve<Room2>();
Debug.Assert(room1 == room1.Grade.Room); // No cyclic dependency in constructor
var grade2 = container.Resolve<Grade2>();
Debug.Assert(grade2 == grade2.Room.Grade); try
{
// Cyclic dependency in constructor
// The call path is: Grade => Room => Grade
var grade3 = container.Resolve<Grade3>();
}
catch (Exception ex)
{
Debug.Assert(ex is CyclicDependencyException);
}
}
}
}
在示例代码中,我们设计了三对 Grade/Room 类,用来演示三种不同的情况。
先来看 Grade1/Room1。
public class Grade1
{
public Room1 Room { get; set; }
}
public class Room1
{
public Grade1 Grade { get; set; }
}
我们看到这两个类的属性相互引用对方,但它们都没有定义构造函数。这里,我们以 Grade1 为例来分析一下它们的构造过程。
当我们需要一个 Grade1 对象时,容器首先调用其构造函数为我们创建一个 Grade1 对象。创建好 Grade1 对象之后,我们看到它需要注入一个 Room1 类型的属性。这样容器又会为我们创建一个 Room1 对象。随后,我们看到 Room1 也需要注入一个 Grade1 类型的属性。此时,由于之前已经创建好一个 Grade1 对象,因此容器在这里便会复用该 Grade1 对象(这一点很关键),以完成 Room1 对象的创建。随着 Room1 对象创建完成,容器跟着将该对象注入到 Grade1 的 Room 属性中,这样 Grade1 也创建完成了。因此,这里不会导致循环依赖,所以我们运行下面的代码不会导致异常:
var grade1 = container.Resolve<Grade1>();
Debug.Assert(grade1 == grade1.Room.Grade);
var room1 = container.Resolve<Room2>();
Debug.Assert(room1 == room1.Grade.Room);
接下来,我们看 Grade2/Room2。
public class Grade2
{
private readonly Room2 _room; public Grade2(Room2 room)
{
_room = room;
room.Grade = this;
} public Room2 Room
{
get { return _room; }
}
}
public class Room2
{
public Grade2 Grade { get; set; }
}
当我们构造 Grade2 对象时,并不会引起循环依赖。因为我们看到 Grade2 虽然在构造函数中需要 Room2,但在构造 Room2 的过程中并不需要 Grade2(因为 Room2 没有定义构造函数,而且也不需要在自身的构造过程中注入 Grade2 这个属性),所以不会有循环依赖问题。
最后我们来看 Grade3/Room3。
public class Grade3
{
private readonly Room3 _room; public Grade3(Room3 room)
{
_room = room;
} public Room3 Room
{
get { return _room; }
}
}
public class Room3
{
public Grade3 Grade { get; set; }
}
我们看到 Grade3 在构造函数中需要一个 Room3 类型参数,而 Room3 类型虽然没有定义构造函数,但它在构造过程中要求注入一个 Grade3 类型的属性值。这样就引起了问题。因为当 Grade3 需要一个 Room3 对象的时候,Room3 对象尚未创建,这样容器就需要先创建一个 Room3 对象。但容器在创建 Room3 对象的过程中,又需要注入一个 Grade3 对象。然而此时最初的 Grade3 尚未创建完成(其构造函数已经被调用,但尚未完成),还无法在此处复用。因此,容器又要再创建一个新的 Grade3 对象以满足 Room3 的构造需要。随后,这个新的 Grade3 又需要一个新的 Room3,而后面这个新的 Room3 又需要一个新的 Grade3...这样永无休止地纠缠下去,这个构造过程永远不会完成。如果用比较简洁的方式来表述的话,上述创建过程的依赖路径如下:Grade3 => Room3 => Grade3 => Room3 => Grade3....
这样我们就明白了,只要我们在设计类时,注意避免产生如上所述的依赖路径,就能够有效地防止循环依赖的问题。其实,也就是遵循上面那个原则:[不要在对象构造过程中引入循环依赖]。
本文示例代码以及 My.Ioc 框架源码可在此处获取。
My.Ioc 代码示例——避免循环依赖的更多相关文章
- Spring IOC原理补充(循环依赖、Bean作用域等)
文章目录 前言 正文 循环依赖 什么是循环依赖? Spring是如何解决循环依赖的? 作用域实现原理以及如何自定义作用域 作用域实现原理 自定义Scope BeanPostProcessor的执行时机 ...
- My.Ioc 代码示例——如何使用默认构造参数,以及如何覆盖默认构造参数
在 Ioc 世界中,有些框架(例如 Autofac/NInject/Unity)支持传递默认参数,有些框架(例如 SimpleInjector/LightInjector 等)则不支持.作为 My.I ...
- My.Ioc 代码示例——使用观察者机制捕获注册项状态的变化
在 My.Ioc 中,要想在服务注销/注册时获得通知,可以通过订阅 ObjectBuilderRegistered 和 ObjectBuilderUnregistering 这两个事件来实现.但是,使 ...
- My.Ioc 代码示例——实现自动注册/解析
在很多 Ioc 容器中,当使用者向容器请求实现了某个契约类型 (Contract Type) 的服务时 (调用类似如下方法 container.Resolve(Type contractType)), ...
- 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...
- My.Ioc 代码示例——利用 ObjectBuilderRequested 事件实现延迟注册
在使用 Ioc 框架时,一般我们建议集中在一个称为 Composition Root(其含义请参见下面的小注)的位置来注册 (Register) 和解析 (Resolve) 服务.这种做法的目的在于限 ...
- My.Ioc 代码示例——谈一谈如何实现装饰器模式,兼谈如何扩展 My.Ioc
装饰器模式体现了一种“组合优于继承”的思想.当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手. 很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac.在 My.Io ...
- My.Ioc 代码示例——属性和方法注入
在 My.Ioc 中,我们可以指定让容器在构建好对象实例之后,自动为我们调用对象的公共方法或是为对象的公共属性赋值.在解析对象实例时,容器将根据我们在注册对象时指定的方法调用或属性赋值的先后顺序,调用 ...
- My.Ioc 代码示例——使用条件绑定和元数据(可选)构建插件树
本文旨在通过创建一棵插件树来演示条件绑定和元数据的用法. 说“插件树”也许不大妥当,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单.举例来说,如果要在 Winform ...
随机推荐
- BZOJ 2572 高速公路
Description Y901高速公路是一条重要的交通纽带,政府部门建设初期的投入以及使用期间的养护费用都不低,因此政府在这条高速公路上设立了许多收费站.Y901高速公路是一条由N-1段路以及N个收 ...
- QS Network
zoj1586:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1586 题目大意:最小生成树,不只算两点之间的费用,还要算点 ...
- vs2012 aps.net4.0/4.5尚未在web服务器上注册
安装了vs2015后,vs2012 启动后报错: aps.net4.0/4.5尚未在web服务器上注册 解决办法: 下载微软补丁: http://blogs.msdn.com/b/webdev/arc ...
- Ubuntu 12.04 使用Eclipse搭建C/C++编译环境
首先是安装Eclipse,方法有两种: 第一种是通过Ubuntu自带的程序安装功能安装Eclipse,应用程序->Ubtuntu软件中心,搜Eclipse安装即可. 第二 ...
- 【模拟】Codeforces 707A Brain's Photos
题目链接: http://codeforces.com/problemset/problem/707/A 题目大意: 给一张N*M的图,只有六种颜色(如下),只含B,W,G的是黑白图,否则是彩色图.问 ...
- SQL中ISNULL的使用
在敲写相关sql语句时,我们经常会遇到一些空的字符串或者是字段,这就给我们对数据库造成一定的麻烦,系统经常会提示“某值null不能转换”“插入的值不能为空”等等诸如此类的提示,isnull函数会帮助你 ...
- Linux Zynq GPIO中断
注册中断:对每个pin进行循环遍历for (pin_num = 0; pin_num < min_t(int, ZYNQ_GPIO_NR_GPIOS, (int)chip->ngpio) ...
- Android为应用在桌面添加一个快捷方式
Intent addIntent=new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); Parcelable ic ...
- drp用户管理完成后,asp.net与java的一个简单比较
DRP视频断断续续看了有一个月的时间了,跟着视频进行,从需求到设计,到现在的编码实现,跟之前用asp.net做系统步调一致,都遵守软件设计的规范,一步步来进行.尤其是编码实现,越来越感觉java与as ...
- angularJS promise $q
Promise 一 介绍 1.什么是promise 我们知道JavaScript语言的执行环境是“单线程”,所谓单线程,就是一次只能够执行一个任务,如果有多个任务的话就要排队,前面一个任务完成后才可以 ...