Aoite 系列(02) - 超动感的 Ioc 容器

时间:2021-08-02 15:33:40

Aoite 系列(02) - 超动感的 Ioc 容器

Aoite 是一个适于任何 .Net Framework 4.0+ 项目的快速开发整体解决方案。Aoite.Ioc 是一套解决依赖的最佳实践。

说明: Aoite 是一套快速开发整体解决方案。它不是只有 ORM 或者 Ioc 之类的。框架的内容还是算有点庞大。我需要一点一点的将文章和教程编写出来,如果加上将其每一部分和其他框架进行比较更需要花费时间。所以所有的入门篇都会简单的介绍用法,目的是让使用人员快速入门。若是您想要更快的了解这套框架,可以从单元测试入手。

【Aoite 系列 目录】

赶紧加入 Aoite GitHub 的大家庭吧!!

1. 快速入门

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则。IoC的概念已经提出了非常多年了。

如果...如果...你对 Ioc 不是很理解的话,我们可以这么理解:

Aoite 系列(02) - 超动感的 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 模块有一套类型映射的策略。它是这样的一步一步的匹配:

  1. 判定 IWelcome 是否已经手工注册(container.AddService)。
  2. 判定上级 Ioc 容器是否已手工注册 IWelcome
  3. 判定是否禁用了自动解析的功能(IocContainer.DisabledAutoResolving),成立则直接返回 null 值。
  4. 判定 IWelcome 是否定义了 DefaultMappingAttribute 特性。
  5. 判定是否为基类或值类型,成立则直接返回 null 值。
  6. 尝试触发 IocContainer.MapResolve 事件获取映射类型。
  7. 尝试触发 ObjectFactory.MapResolve 静态事件获取映射类型。
  8. 如果以上条件都找不到映射的类型,将会从当前所有已加载的程序集中满足以下条件的类型(优先级从上至下):
    • namespace.DefaultWelcome
    • namespace.Welcome
    • namespace.FakeWelcome
    • namespace.MockWelcome
  9. 如果以上的条件无法满足,将会返回一个 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 一下 :)