依赖注入框架的C#实现(连载中)--之一兵马未动,测试先行。 本帖最后由 hWonner 于 2012-07-31 05:42:26 编辑 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 不错,看了半天才明白... ...先从Container.get<StubService>()获取StubService接口实例,然后通过service.Repository.ShouldBeOfType<StubRepositoryImpl>()获取StubService接口下的StubRepositoryImpl这个实现类... ...不知道理解的对不对... ..不过建议楼主代码再规范一点,更好,本文可以作为IOC的入门教科书了... ... 1. 整个实现还在连载中, 没有完全。 请看依赖注入框架的C#实现(连载之二)赤裸裸的实现2。 我更多的是想描述实现的过程,而不仅仅是结果。3。 我想你说的自定义类型,是指Mechine.Specification吧,一则,这不是我的代码的一部分,第二,Machine.Specification (BDD)本身内容太多,你可以Google搜,我就不在这罗嗦了。 之二: 赤裸裸的实现完整代码1。 依赖关系就是使用关系。2。依赖关系的解耦,需要用接口查找实现,字典是最好的工具。3. 以下就是赤裸裸的实现,不堪如目,但是简单好理解。最主要是可以让测试通过!!!先放接口,便于理解:namespace Skight.LightWeb.Domain { public interface Resolver { Dependency get<Dependency>(); } } 这是实现:using System;using System.Collections.Generic;namespace Skight.LightWeb.Domain{ public class ResolverImpl:Resolver { private readonly IDictionary<Type, object> item_resolvers; public ResolverImpl(IDictionary<Type, object> itemResolvers) { item_resolvers = itemResolvers; } public Dependency get<Dependency>() { return (Dependency) item_resolvers[typeof (Dependency)]; } }然后测试也要作出相应的修改。using System;using System.Collections.Generic;using Machine.Specifications;namespace Skight.LightWeb.Domain.Specs{ public class ResolverSpecs { private Establish context = () => { var dictioary = new Dictionary<Type, object>(); dictioary.Add(typeof (MockInterface), new MockImplementaion()); subject = new ResolverImpl(dictioary); }; private It Container_get_by_interface_should_return_its_implementation_class = () => subject.get<MockInterface>().ShouldBeOfType<MockImplementaion>(); private static ResolverImpl subject; private interface MockInterface { } private class MockImplementaion : MockInterface { } } } 之三: 注册机的引入应用SRP原则,拆分为:解析器 Resolver 和注册器 Registration1。 解析器 Resolver负责解析接口. resolver.get<MockInterface>().ShouldBeOfType<MockImplementaion>()2.注册器,负责把一个类注册为一个接口registration.register<MockInterface, MockImplementation>()看起来,注册器先于解析器工作---还没注册,如何解析? 可是,测试驱动却颠倒了这个顺序。我先实现了解析器,因为,如何用才是我第一关心的。更多代码:1。 先是,注册机的接口定义,很简单namespace Skight.LightWeb.Domain{ public interface Registration { void register<Contract, Implementaion>() where Implementaion : Contract, new(); }}2。 测试:当用注册机注册一个接口到一个类时, 这个<接口-类>对,应该被添加到解析字典中using System;using System.Collections.Generic;using Machine.Specifications;namespace Skight.LightWeb.Domain.Specs{ public class When_use_Registration_to_register_an_infterface_to_a_class { private Establish context = () => { resolver_dictionary = new Dictionary<Type, object>(); subject = new RegistrationImpl(resolver_dictionary); }; private Because of = () => subject.register<MockInterface, MockImplementation>(); private It the_key_value_paire_should_be_added_to_resovler_dictionary = () => resolver_dictionary[typeof (MockInterface)].ShouldBeOfType<MockImplementation>(); private static RegistrationImpl subject; private static Dictionary<Type, object> resolver_dictionary; private interface MockInterface{} private class MockImplementation:MockInterface {} }}3。 然后是实现:using System;using System.Collections.Generic;namespace Skight.LightWeb.Domain{ public class RegistrationImpl:Registration { private IDictionary<Type, object> item_resolvers; public RegistrationImpl(IDictionary<Type, object> item_resolvers) { this.item_resolvers = item_resolvers; } public void register<Contract, Implementaion>() where Implementaion : Contract, new() { item_resolvers.Add(typeof(Contract), new Implementaion()); } }} 是的! 简单介绍一下背景:1. 是行为驱动框架,基于已有的测试框架nUnit,用AAA语法重新封装。2. AAA: 所有测试分成三部分: 1> 测试的组织(建立测试环境) Arrange 2> 测试本身, Action 3> 断言 Assert分别对应MSpec术语: Context, Because, It 与nUnit不同,MSpec每个测试是一个类,而不是一个方法。这样,测试能够继承等等利用上类的功能,使得测试本身能够设计规划。3. Git是一个开源的源代码管理工具,Linux作者写的,比较新,我看好它的底层设计,GitHub.com是用Git搭建的开源代码网站。你不需要下载Git就可以直接下载,有个Zip按钮。 当然,下了Git会更好,还有,如果注册账号,你还可以参与到代码的修改。你可以直接Fork任何其他人的开源项目,就像变成你自己的一样,然后,可以在该基础上,任意修改,就像你自己的项目一样。 最后, 你还可以把你的修改提交给原作者,他可以你的修改合并到他的原项目中去。 这样,为开源的协作提供更好的机制。 先从Container.get<StubService>()获取StubService接口实例, ==》这就是对注入框架的调用。service.Repository.ShouldBeOfType<StubRepositoryImpl>() ==》 这个是测试,当我手工从Container获得 StubService实例时,这个实例所依赖(使用)的另外一个接口(StubRepository)应该自动注入。这里, ShouldBeOfType<StubRepositoryImple>是个断言,翻译成nUnit==> Assert.AreEqual(typeof(StubRepositoryImpl), type(service.Repository));, 继续:前文虽繁琐,核心内容却也简单。一个字典:IDictionary<Type, object> item_resolvers一个注册机: public interface Registration { void register<Contract, Implementaion>() where Implementaion : Contract, new(); }一个解析器: public interface Resolver { Dependency get<Dependency>(); }后两者的构造器,以字典为参数,从而穿在一起。 public RegistrationImpl(IDictionary<Type, DiscreteItemResolver> item_resolvers) { this.item_resolvers = item_resolvers; } public ResolverImpl(IDictionary<Type, DiscreteItemResolver> itemResolvers) { item_resolvers = itemResolvers; } 如果你成功的下载源代码,成功的配置好Machine.Specification的测试插件,并成功的运行了测试后,你就会发现,仍然有一个测试没有通过,甚至连测试本身都不完整,这个测试就是递归依赖的解析。分析过程不罗嗦,结论是字典的定义产生的瓶颈。字典值,原来定义为object,现在改为DiscreteItemResolver, 定义如下,没有巨大的变化,需要取object时,通过调用接口方法resolve()。但是,架构方面的变化是巨大的,这是一个典型的后期绑定。不是在注册时绑定实现的对象,而是在解析的时候!namespace Skight.LightWeb.Domain{ public interface DiscreteItemResolver { object resolve(); }}接下来,根据这个变化清理代码,所有用字典取Value的地方加一个resolve()所有改动参看: https://github.com/SkightTeam/LightWeb/commit/61df555f5202907d54a86a6374b164d69a86b4c2需要注意的是,原来注册机RegistrationImpl的实现从简单的new 一个对象,改为本地Mock一个DiscreteItemResolver的简单实现。 item_resolvers.Add(typeof(Contract), new Implementaion()); item_resolvers.Add(typeof(Contract), new MockResolverImpl<Implementation>()); 唉,特殊效果不起作用。 item_resolvers.Add(typeof(Contract), new Implementaion()); item_resolvers.Add(typeof(Contract), new MockResolverImpl<Implementation>()); WCF 10049: 在其上下文中,该请求的地址无效,急急急! 请教高手 关于C#类库由tlbexp导出tlb库问题!在线等待 C#如何读取、显示ppm、pbm、pgm格式文件 指点下,谢谢! SQL 怎么查两个值的不同组合(distinct) ASP.NET Configuration不好用了!!!!!!!! dataTabel 读取XML数据问题 listBox自动更新? 谁可以告诉我如何得到MSN和OutLook中的地址?急!! 委派(代理)机制有什么妙用? 问题,麻烦高手说详细点最好解释下 谢谢 WebBrowser InvokeMember('click') 偶尔失灵问题
先从Container.get<StubService>()获取StubService接口实例,
然后通过service.Repository.ShouldBeOfType<StubRepositoryImpl>()获取StubService接口下的StubRepositoryImpl这个实现类... ...
不知道理解的对不对... ..
不过建议楼主代码再规范一点,更好,本文可以作为IOC的入门教科书了... ...
2。 我更多的是想描述实现的过程,而不仅仅是结果。
3。 我想你说的自定义类型,是指Mechine.Specification吧,一则,这不是我的代码的一部分,第二,Machine.Specification (BDD)本身内容太多,你可以Google搜,我就不在这罗嗦了。
完整代码
1。 依赖关系就是使用关系。
2。依赖关系的解耦,需要用接口查找实现,字典是最好的工具。
3. 以下就是赤裸裸的实现,不堪如目,但是简单好理解。最主要是可以让测试通过!!!
先放接口,便于理解:namespace Skight.LightWeb.Domain { public interface Resolver { Dependency get<Dependency>(); } } 这是实现:using System;
using System.Collections.Generic;namespace Skight.LightWeb.Domain
{
public class ResolverImpl:Resolver
{
private readonly IDictionary<Type, object> item_resolvers; public ResolverImpl(IDictionary<Type, object> itemResolvers)
{
item_resolvers = itemResolvers;
} public Dependency get<Dependency>()
{
return (Dependency) item_resolvers[typeof (Dependency)];
}
}然后测试也要作出相应的修改。
using System;
using System.Collections.Generic;
using Machine.Specifications;namespace Skight.LightWeb.Domain.Specs
{
public class ResolverSpecs
{
private Establish context =
() =>
{
var dictioary = new Dictionary<Type, object>();
dictioary.Add(typeof (MockInterface), new MockImplementaion());
subject = new ResolverImpl(dictioary);
}; private It Container_get_by_interface_should_return_its_implementation_class =
() => subject.get<MockInterface>().ShouldBeOfType<MockImplementaion>(); private static ResolverImpl subject;
private interface MockInterface { }
private class MockImplementaion : MockInterface { }
}
}
应用SRP原则,拆分为:解析器 Resolver 和注册器 Registration1。 解析器 Resolver负责解析接口.
resolver.get<MockInterface>().ShouldBeOfType<MockImplementaion>()
2.注册器,负责把一个类注册为一个接口
registration.register<MockInterface, MockImplementation>()看起来,注册器先于解析器工作---还没注册,如何解析? 可是,测试驱动却颠倒了这个顺序。我先实现了解析器,因为,如何用才是我第一关心的。更多代码:
1。 先是,注册机的接口定义,很简单namespace Skight.LightWeb.Domain
{
public interface Registration
{
void register<Contract, Implementaion>() where Implementaion : Contract, new();
}
}2。 测试:
当用注册机注册一个接口到一个类时,
这个<接口-类>对,应该被添加到解析字典中
using System;
using System.Collections.Generic;
using Machine.Specifications;namespace Skight.LightWeb.Domain.Specs
{
public class When_use_Registration_to_register_an_infterface_to_a_class
{
private Establish context =
() =>
{
resolver_dictionary = new Dictionary<Type, object>();
subject = new RegistrationImpl(resolver_dictionary);
}; private Because of =
() => subject.register<MockInterface, MockImplementation>(); private It the_key_value_paire_should_be_added_to_resovler_dictionary =
() => resolver_dictionary[typeof (MockInterface)].ShouldBeOfType<MockImplementation>();
private static RegistrationImpl subject;
private static Dictionary<Type, object> resolver_dictionary; private interface MockInterface{}
private class MockImplementation:MockInterface {}
}
}
3。 然后是实现:
using System;
using System.Collections.Generic;namespace Skight.LightWeb.Domain
{
public class RegistrationImpl:Registration
{
private IDictionary<Type, object> item_resolvers;
public RegistrationImpl(IDictionary<Type, object> item_resolvers)
{
this.item_resolvers = item_resolvers;
} public void register<Contract, Implementaion>() where Implementaion : Contract, new()
{
item_resolvers.Add(typeof(Contract), new Implementaion());
}
}
}
2. AAA: 所有测试分成三部分:
1> 测试的组织(建立测试环境) Arrange
2> 测试本身, Action
3> 断言 Assert
分别对应MSpec术语: Context, Because, It
与nUnit不同,MSpec每个测试是一个类,而不是一个方法。这样,测试能够继承等等利用上类的功能,使得测试本身能够设计规划。
3. Git是一个开源的源代码管理工具,Linux作者写的,比较新,我看好它的底层设计,GitHub.com是用Git搭建的开源代码网站。你不需要下载Git就可以直接下载,有个Zip按钮。 当然,下了Git会更好,还有,如果注册账号,你还可以参与到代码的修改。你可以直接Fork任何其他人的开源项目,就像变成你自己的一样,然后,可以在该基础上,任意修改,就像你自己的项目一样。 最后, 你还可以把你的修改提交给原作者,他可以你的修改合并到他的原项目中去。 这样,为开源的协作提供更好的机制。
service.Repository.ShouldBeOfType<StubRepositoryImpl>() ==》 这个是测试,当我手工从Container获得 StubService实例时,这个实例所依赖(使用)的另外一个接口(StubRepository)应该自动注入。
这里, ShouldBeOfType<StubRepositoryImple>是个断言,翻译成nUnit==> Assert.AreEqual(typeof(StubRepositoryImpl), type(service.Repository));,
前文虽繁琐,核心内容却也简单。
一个字典:
IDictionary<Type, object> item_resolvers
一个注册机: public interface Registration
{
void register<Contract, Implementaion>() where Implementaion : Contract, new();
}一个解析器: public interface Resolver
{
Dependency get<Dependency>();
}后两者的构造器,以字典为参数,从而穿在一起。 public RegistrationImpl(IDictionary<Type, DiscreteItemResolver> item_resolvers)
{
this.item_resolvers = item_resolvers;
} public ResolverImpl(IDictionary<Type, DiscreteItemResolver> itemResolvers)
{
item_resolvers = itemResolvers;
}
分析过程不罗嗦,结论是字典的定义产生的瓶颈。
字典值,原来定义为object,现在改为DiscreteItemResolver, 定义如下,没有巨大的变化,需要取object时,通过调用接口方法resolve()。但是,架构方面的变化是巨大的,这是一个典型的后期绑定。不是在注册时绑定实现的对象,而是在解析的时候!
namespace Skight.LightWeb.Domain
{
public interface DiscreteItemResolver
{
object resolve();
}
}接下来,根据这个变化清理代码,所有用字典取Value的地方加一个resolve()
所有改动参看: https://github.com/SkightTeam/LightWeb/commit/61df555f5202907d54a86a6374b164d69a86b4c2
需要注意的是,原来注册机RegistrationImpl的实现从简单的new 一个对象,改为本地Mock一个DiscreteItemResolver的简单实现。
item_resolvers.Add(typeof(Contract),
new Implementaion());item_resolvers.Add(typeof(Contract), new MockResolverImpl<Implementation>());
item_resolvers.Add(typeof(Contract), new Implementaion());
item_resolvers.Add(typeof(Contract), new MockResolverImpl<Implementation>());