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 一下 :)