Aoite 系列(02) - 超动感的 Ioc 容器
Aoite 系列(02) - 超动感的 Ioc 容器
Aoite 是一个适于任何 .Net Framework 4.0+ 项目的快速开发整体解决方案。Aoite.Ioc 是一套解决依赖的最佳实践。
说明: Aoite 是一套快速开发整体解决方案。它不是只有 ORM 或者 Ioc 之类的。框架的内容还是算有点庞大。我需要一点一点的将文章和教程编写出来,如果加上将其每一部分和其他框架进行比较更需要花费时间。所以所有的入门篇都会简单的介绍用法,目的是让使用人员快速入门。若是您想要更快的了解这套框架,可以从单元测试入手。
赶紧加入 Aoite GitHub 的大家庭吧!!
1. 快速入门
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则。IoC的概念已经提出了非常多年了。
如果...如果...你对 Ioc 不是很理解的话,我们可以这么理解:

秒懂了?不懂也没关系,反正我也没打算在这里长篇大论的讲解什么是 IoC。网上有许多关控制反转、依赖注入的相关文章。我就不在这里误人子弟了 :)
和其他 Ioc 框架先不做比较。Aoite.Ioc 比较有意思的一点是:提倡的是无配置化模式。
我们还是赶紧通过代码快速了解 Aoite 的 IoC 模块。
interface IWelcome
{
    string GetHelloText();
}
class DefaultWelcome : IWelcome
{
    public string GetHelloText()
    {
        return "Hello World!";
    }
}
class ChineseWelcome : IWelcome
{
    public string GetHelloText()
    {
        return "你好,世界!";
    }
}
private static void Demo1()
{
    IocContainer container = new IocContainer();
    Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
}
是的,仅仅只有这样的代码,当你调用了 Demo1 方法后,它将会直接输出“Hello World!”文字。
1.1 它是怎么动感的
Aoite.Ioc 模块有一套类型映射的策略。它是这样的一步一步的匹配:
- 判定 
IWelcome是否已经手工注册(container.AddService)。 - 判定上级 Ioc 容器是否已手工注册 
IWelcome。 - 判定是否禁用了自动解析的功能(
IocContainer.DisabledAutoResolving),成立则直接返回 null 值。 - 判定 
IWelcome是否定义了DefaultMappingAttribute特性。 - 判定是否为基类或值类型,成立则直接返回 null 值。
 - 尝试触发 
IocContainer.MapResolve事件获取映射类型。 - 尝试触发 
ObjectFactory.MapResolve静态事件获取映射类型。 - 如果以上条件都找不到映射的类型,将会从当前所有已加载的程序集中满足以下条件的类型(优先级从上至下):
- namespace.DefaultWelcome
 - namespace.Welcome
 - namespace.FakeWelcome
 - namespace.MockWelcome
 
 - 如果以上的条件无法满足,将会返回一个 null 值。
 
所以为了我们可以将代码改成这样,代替默认的 DefaultWelcome。
private static void Demo1()
{
    IocContainer container = new IocContainer();
    container.AddService<IWelcome, ChineseWelcome>(); /* 手工注册 */
    Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
}
小技巧:通过 IocContainer.MapResolve 事件或 ObjectFactory.MapResolve 静态事件,你可以应用到 WCF、Remoting 等场景。
1.2 单例模式
讲解单例模式之前,我们先来做一个测试:
 private static void Demo1()
 {
     IocContainer container = new IocContainer();
     container.AddService<IWelcome, ChineseWelcome>();
     Console.WriteLine(container.GetService<IWelcome>() == container.GetService<IWelcome>());
 }
它输出的是 False。为什么会这样呢?原因是默认情况下,IocContainer 并不会将类型单例化。因为它无法准确判断你是要创建一个对象,还是每次调用都创建一个新的对象。
所以,如果要单例,你可以尝试以下几种方式:
接口特性,这样的方式会导致获取这个接口的所有类型,都采用单例模式。
[SingletonMapping]
interface IWelcome
{
    //......
}
类特性,只有映射到这个类型,才会成为单例模式。
[SingletonMapping]
class ChineseWelcome : IWelcome
{
    //......
}
注册约定
container.AddService<IWelcome, ChineseWelcome>(true /* singletonMode */);
1.3 懒加载
有时候,我们需要一个类似 Lazy 的懒加载方式,或者你需要根据不同的后期绑定参数,返回不同的类型。你可以这样折腾:
IocContainer container = new IocContainer();
container.AddService<IWelcome>(lmps =>
{
    if(lmps == null || lmps.Length == 0) return new DefaultWelcome();
    return new ChineseWelcome();
});
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
Console.WriteLine(container.GetService<IWelcome>("abc").GetHelloText());
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
Console.WriteLine(container.GetService<IWelcome>(1).GetHelloText());
这样的话,输出的内容便是:
Hello World!
你好,世界!
Hello World!
你好,世界!
小技巧:通过 InstanceCreatorCallback 委托,可以很灵活的创建对象。
// 摘要:
//     表示实例创建的委托。
//
// 参数:
//   lastMappingArguments:
//     后期绑定的参数列表。
//
// 返回结果:
//     返回一个实例。
public delegate object InstanceCreatorCallback(object[] lastMappingArguments);
2. 进阶内容
2.1 Key-Value 的映射
IocContainer 除了支持对类型的支持外,还支持类似配置参数的方式。比如你可以这样玩:
IocContainer container = new IocContainer();
container.AddValue("a", 1);
container.AddValue("b", 2);
Console.WriteLine(container.GetValue("a"));
Console.WriteLine(container.GetValue("b"));
Console.WriteLine(container.GetValue("c") ?? "<NULL>");
这样有什么意义吗?第一个意义是可以依赖倒置某些简单的配置信息。比如数据库连接字符串、Redis 的连接地址之类。除此之外,还有其他意义吗?
答案是:有!
2. 带参数的构造函数
假设我们新增了一种 Welcome 类型:
class CustomWelcome : IWelcome
{
    private string _welcomeText;
    public CustomWelcome(string welcomeText)
    {
        this._welcomeText = welcomeText;
    }
    public string GetHelloText()
    {
        return "Oh~" + this._welcomeText;
    }
}
那么我们该如何映射呢?搜一鸡!还支持多种姿势!
第一种 后期映射
class CustomWelcome : IWelcome
{
    //.....
    public CustomWelcome([LastMapping]string welcomeText)
    //.....
}
指定了 LastMappingAttribute 表示这个参数允许通过后期绑定来赋值。这个特性还可以装载在类或接口上,表示这个类型/接口如果用在构造函数的话,都会被当作后期绑定参数。
IocContainer container = new IocContainer();
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>("自定义欢迎语。").GetHelloText());
第二种 预配模式 不需要加上 LastMappingAttribute 特性,直接通过 Key-Value 映射(指定目标类型优先,并且若存在上级容器,将会寻找到上级容器)。
IocContainer container = new IocContainer();
container.AddValue("welcomeText", "这是一种鸟语的欢迎语。");
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
第三种 智能模式 适用于类似以下的业务场景:
public class AccountController : Controller
{
    public AccountController(IUserRepository userRepository)
    //.....
}
userRepository 参数并不需要指定特性 LastMappingAttribute,甚至无需预配 IUserRepository 接口的映射类型。一气呵成,浑然天成。
需要说明的是,映射的优先级也是从第一种到最后一种。
2.3 Key-Value 的针对性映射
显然 2.1 中的方式虽然好用,但有些场景却不适合,比如说不同类型相同参数名称的场景。这个时候,就可以采用以下方法:
IocContainer container = new IocContainer();
container.AddValue<CustomWelcome>("welcomeText", "这是一种鸟语的欢迎语。");
//- 或 container.AddValue<IWelcome>(...);
container.AddService<IWelcome, CustomWelcome>();
Console.WriteLine(container.GetService<IWelcome>().GetHelloText());
2.4 强制性要求手工注册
有些场景,我们不希望通过智能解析映射。而是在确保未注册情况下返回 null 值。这个时候就需要用到 GetFixedService 方法。这个方法不会智能去解析,它只会判断是否已经在注册列表,如果没有,直接返回 null 值。
2.5 更多
- Parent:上级容器
 - ServiceTypes:所有服务类型。
 - TypeValueNames:所有绑定到类型的值的名称。
 - ValueNames:所有值的名称。
 - DestroyAll():销毁所有的映射。
 - CreateChildLocator():创建基于当前服务容器的子服务容器。
 - ContainsXXXX:判断指定的类型或值是否已注册。
 - RemoveXXXX:删除指定的类型或值。
 
3. 结束
关于 Aoite.Ioc 的简单介绍,就到此结束了,如果你喜欢这个框架,不妨点个推荐吧!如果你非常喜欢这个框架,那请顺便到Aoite GitHub Star 一下 :)
Aoite 系列(02) - 超动感的 Ioc 容器的更多相关文章
- Aoite 系列 目录
		
介绍 本项目从2009年孵化(V->Sofire->Aoite),至今已度过5个年头.一直在优化,一直在重构,一直在商用.有十分完整的单元测试用例.可以放心使用. Aoite on 博客园 ...
 - TypeC一个微软开发的超简单.NET依赖注入/IoC容器
		
控制反转(IoC,Inversion of Control)是由Martin Fowler总结出来的一种设计模式,用来减少代码间的耦合.一般而言,控制反转分为依赖注入(Dependency Injec ...
 - Spring IOC 容器源码分析系列文章导读
		
1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...
 - Spring IOC 容器源码分析 - 余下的初始化工作
		
1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...
 - Spring IOC 容器源码分析 - 填充属性到 bean 原始对象
		
1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...
 - Spring IOC 容器源码分析 - 循环依赖的解决办法
		
1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...
 - Spring IOC 容器源码分析 - 创建原始 bean 对象
		
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
 - Spring IOC 容器源码分析 - 创建单例 bean 的过程
		
1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...
 - Spring IOC 容器源码分析 - 获取单例 bean
		
1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...
 
随机推荐
- 磊科NI360路由器绕过密码登录
			
先分析正确登陆360路由器后cookies是怎么样的,发现只有一个值如下图: 可以看出登陆后只有一个netcore_login=guest:1 下面来模拟一下这个cookies看是否能登陆 增加好后直 ...
 - C/C++中extern关键字解析
			
1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...
 - Jquery插件开发精品教程
			
最开始接触jquery对他提供的各种插件总是十分有兴趣,但是总是不理解为什么这样写,从网络上查询了很久终于找到这篇文章,讲解的很详细,分享给大家. 要说jQuery 最成功的地方,我认为是它的可扩展性 ...
 - C#开发Android环境搭建
			
目前破解比较稳定的版本(我亲自尝试过的)是4.2. wuleba上的4.6,4.8,4.10 破解均会出现各种问题. 1 当前电脑账户最好是使用英文账号,而不要使用汉字,否则路径会出现乱码问题. 2 ...
 - BZOJ1261: [SCOI2006]zh_tree
			
Description 张老师根据自己工作的需要,设计了一种特殊的二叉搜索树.他把这种二叉树起名为zh_tree,对于具有n个结点的zh_tree,其中序遍历恰好为(1,2,3,-,n),其中数字1, ...
 - 15.Xcode8 升级遇到的问题
			
一:注释快捷键cmd+/不能用,解决方法: 1. Swift_3.0 没法快捷键(command+/)注释的原因:这个是因为苹果解决xcode ghost,把插件屏蔽了. 2. 解决办法: (1) 终 ...
 - 转 :meta name的含义:<META http-equiv=Content-Type content="text/html; charset=gb2312">
			
meta是什么?meta其实是html语言head区的一个辅助性标签.在几乎所有的网页里,我们都可以看到类似下面这段html代码:<META http-equiv=Content-Type co ...
 - TypeError: matchExpr[type].exec is not a function
			
遇到了这个问题,很久没找到答案,后来使用了万能的google,貌似也没找到答案. 详细描述下: 通过使用 $(".select")来选择jqeury对象,没问题. 通过$(&quo ...
 - oracle基本操作
			
登入oraclesqlplus / as sysdba启动oraclestartup停止oracleshutdown 创建新用户create user username identified by p ...
 - [转]Java学习日记之 volatile
			
用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B.只在某些动作时才进行A和B的同步.因此存在A和B不一致的情况.volatile就是用来 ...