抽象
在过去的几个月中,我一直在编程重量轻,基于C#的游戏引擎与API抽象和实体/组件/脚本系统。 它的整体思路是,以减轻游戏开发过程中的XNA,SlimDX和这样的,通过提供类似于Unity引擎的架构。
设计挑战
由于大部分的游戏开发商都知道,有很多的,你需要在你的代码来访问不同的服务 。 许多开发人员求助于使用的例如一个全球性的静态实例渲染经理(或作曲家),场景,GraphicsDevice的(DX),记录器,输入状态,视窗,窗等。 有一些替代方法,以全局静态实例/单身。 一个是给每个类它需要访问的类的一个实例,无论是通过一个构造或构造/属性依赖注入(DI),另一个是使用全球服务定位器,像StructureMap的的ObjectFactory其中服务定位器通常被配置为IoC容器。
依赖注入
我选择去的DI方式的原因有很多。 最明显的一个是可测性,通过编程对接口,并通过构造函数提供给他们的每一个类的所有相关性,这些类很容易测试,因为测试容器可以实例化所需的服务,或者他们的嘲笑,并送入每类进行测试。 另一个原因做DI /国际奥委会,无论你相信与否,增加代码的可读性。 实例所有不同的服务和手动到所需服务的引用实例化类没有更多的巨大的初始化过程。 配置内核(NInject)/注册表(StructureMap)方便地给出了配置用于发动机/游戏,其中服务实现被拾取并且被配置为单个点。
我的问题
- 我常常觉得我创造了接口的缘故接口
- 我的工作效率已经大幅下降,因为所有我做的是如何做事的DI-的方式,而不是快速和简单的全局静态的方式忧虑。
- 在某些情况下,例如在运行时实例化新的实体时,需要访问IoC容器/内核创建实例。 这将创建IoC容器本身(的ObjectFactory在SM,在Ninject内核的实例),这实际上违背了原因首先使用一个有依赖性。 这又如何解决? 摘要工厂浮现在脑海中,但该代码只是进一步复杂化。
- 根据服务要求,某些类的构造可以得到非常大的,这将使得类在,如果不使用一个IoC其他情况下完全没用。
基本上是做DI / IoC的显着减慢我的工作效率,并在某些情况下,代码和架构的进一步复杂化。 因此,我不确定它是否是我应该遵循,或干脆放弃,做事情的老式方法的路径。 我不是在寻找一个答案说什么我应该或不应该做的,但如果在使用DI的讨论是值得的,而不是使用全局静态/单身的方式从长远来看,可能利弊我忽略和我的问题可能的解决方案上面列出,DI打交道时。
如果你回到老式的方法是什么? 我在简短的答案是否定的。 DI对你提到的所有原因,许多好处。
我常常觉得我创造了接口的缘故接口
如果你这样做,你可能会违反回用抽象原则(RAP)
根据服务要求,某些类的构造可以得到非常大的,这将使得类在,如果不使用一个IoC其他情况下完全没用。
如果你的类的构造函数过于庞大和复杂,这是展示你最好的方式,你违反了一个非常重要的原则,其他: 单个Reponsibility原则 。 在这种情况下,它是时间提取和重构你的代码到不同的类别,建议依赖关系的数量大约是4。
为了做到DI你不必有一个接口,DI就是你得到你的依赖到你的对象的方式。 创建的界面可能是一个必要的方式能够替代依赖用于测试目的。 除非依赖的对象是:
- 轻松隔离
- 不说话外部子系统(文件系统等)
你可以创建你所依赖的是一个抽象类,或者你想替代方法是虚拟的任何一类。 但是接口做创造一个依赖的最好的去耦合方式。
在某些情况下,例如在运行时实例化新的实体时,需要访问IoC容器/内核创建实例。 这将创建IoC容器本身(的ObjectFactory在SM,在Ninject内核的实例),这实际上违背了原因首先使用一个有依赖性。 这又如何解决? 摘要工厂浮现在脑海中,但该代码只是进一步复杂化。
至于依赖于IOC容器,你不应该在你的客户端类依赖于它。 他们不必。
为了第一次使用依赖注入正确的理解这个概念组成的根 。 这是你的容器应参考的唯一地方。 在这一点上你的整个对象图构造。 一旦你明白这一点,你会发现,你永远不需要在客户端的容器。 由于每个客户端刚刚获得的依赖注入。
也有很多其他的创建模式可以遵循,使施工更简单:假设你想构建具有很多依赖这样的对象:
new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
您可以创建一个具体的工厂,它知道如何构建这样的:
public static class SomeBusinessObjectFactory
{
public static SomeBusinessObject Create()
{
return new SomeBusinessObject(
new SomethingChangedNotificationService(new EmailErrorHandler()),
new EmailErrorHandler(),
new MyDao(new EmailErrorHandler()));
}
}
然后用它是这样的:
SomeBusinessObject bo = SomeBusinessObjectFactory.Create();
您还可以使用迪芒差,创建一个构造函数,在所有不带任何参数:
public SomeBusinessObject()
{
var errorHandler = new EmailErrorHandler();
var dao = new MyDao(errorHandler);
var notificationService = new SomethingChangedNotificationService(errorHandler);
Initialize(notificationService, errorHandler, dao);
}
protected void Initialize(
INotificationService notifcationService,
IErrorHandler errorHandler,
MyDao dao)
{
this._NotificationService = notifcationService;
this._ErrorHandler = errorHandler;
this._Dao = dao;
}
然后,它似乎只是它用来工作:
SomeBusinessObject bo = new SomeBusinessObject();
当使用默认的实现是在外部第三方库穷人的DI被认为是不好的,但不那么糟糕,当你有一个很好的默认实现。
那么显然有所有的DI容器,对象制造商等图案。
因此,所有你需要的是思考你的对象了良好的创建模式的。 你的对象本身不应该关心如何创建的依赖,事实上这让他们更加复杂,并使它们混合料2类型的逻辑。 所以,我不敢相信使用DI应该有生产力的损失。
还有一些特殊情况下,你的对象不能只是让注入它的单个实例。 凡寿命一般较短和即时需要的实例。 在这种情况下,你应该注入厂到对象的依赖性:
public interface IDataAccessFactory
{
TDao Create<TDao>();
}
正如你可以看到这个版本是通用的,因为它可以利用IoC容器来创建不同类型的(请注意,虽然IoC容器仍是不可见的,我的客户)。
public class ConcreteDataAccessFactory : IDataAccessFactory
{
private readonly IocContainer _Container;
public ConcreteDataAccessFactory(IocContainer container)
{
this._Container = container;
}
public TDao Create<TDao>()
{
return (TDao)Activator.CreateInstance(typeof(TDao),
this._Container.Resolve<Dependency1>(),
this._Container.Resolve<Dependency2>())
}
}
请注意,我用的活化剂,即使我有一个IoC容器,这是必须注意的是工厂需要构建对象的新实例,而不是只是假设容器将提供一个新的实例作为对象可以与不同的寿命进行注册(辛格尔顿,ThreadLocal的,等等)。 但是这取决于你使用的是一些能够产生这些工厂为你哪个容器。 但是,如果你有一定的对象与短暂一生注册,您可以简单地解决这个问题。
编辑:添加类抽象工厂的依赖:
public class SomeOtherBusinessObject
{
private IDataAccessFactory _DataAccessFactory;
public SomeOtherBusinessObject(
IDataAccessFactory dataAccessFactory,
INotificationService notifcationService,
IErrorHandler errorHandler)
{
this._DataAccessFactory = dataAccessFactory;
}
public void DoSomething()
{
for (int i = 0; i < 10; i++)
{
using (var dao = this._DataAccessFactory.Create<MyDao>())
{
// work with dao
// Console.WriteLine(
// "Working with dao: " + dao.GetHashCode().ToString());
}
}
}
}
基本上是做DI / IoC的显着减慢我的工作效率,并在某些情况下,代码和架构更加复杂
马克·西曼写的真棒博客关于这个问题,并且回答了这个问题:我对那种问题的第一反应是:你说的松耦合代码更难理解。 比更难?
松耦合和大图片
编辑:最后,我想指出的是,并不是每一个对象和依赖的需求还是应该依赖注入,首先考虑如果您正在使用实际上被认为是依赖什么:
什么是依赖呢?
- 应用程序配置
- 系统资源(时钟)
- 第三方库
- 数据库
- WCF /网络服务
- 外部系统(文件/邮件)
以上任何物体或合作者可以在你的控制,并导致副作用和差异行为并使其很难测试。 这些是要考虑的一个抽象(类/接口),并使用DI的时间。
没有什么相关性,并不真正需要DI?
-
List<T>
- 的MemoryStream
- 字符串/基元
- 叶对象/ DTO的
其中使用需要的对象如上述可以简单地被实例化new
关键字。 我不建议使用DI这种简单的对象,除非有特殊原因。 考虑这样一个问题,如果对象是你的完全控制之下,并不会导致行为的任何附加对象图表或副作用(至少要改变/控制或测试的行为的任何东西)。 在这种情况下,简单地新起来。
我已经发布了大量的链接,马克·西曼的职位,但我真的建议你读他的书和博客文章。