依赖注入和开发效率

2024-01-09

Abstract

在过去的几个月里,我一直在编写一个轻量级、基于 C# 的游戏引擎,具有 API 抽象和实体/组件/脚本系统。它的整体理念是通过提供类似于 Unity 引擎的架构来简化 XNA、SlimDX 等游戏开发过程。

设计挑战

正如大多数游戏开发者所知,有很多不同的services您需要访问整个代码。许多开发人员诉诸于使用全局静态实例,例如渲染管理器(或作曲家)、场景、图形设备(DX)、记录器、输入状态、视口、窗口等。对于全局静态实例/单例有一些替代方法。一种是通过构造函数或构造函数/属性依赖注入 (DI) 为每个类提供需要访问的类的实例,另一种是使用全局服务定位器,例如 StructureMap 的 ObjectFactory,其中服务定位器通常配置为一个 IoC 容器。

依赖注入

我选择走 DI 方式有很多原因。最明显的一个是可测试性,通过针对接口进行编程并通过构造函数向它们提供每个类的所有依赖项,这些类很容易测试,因为测试容器可以实例化所需的服务或它们的模拟,并将其馈送到每堂课都要进行测试。不管你信不信,进行 DI/IoC 的另一个原因是为了提高代码的可读性。不再需要实例化所有不同服务并手动实例化引用所需服务的类的庞大初始化过程。配置内核(NInject)/注册表(StructureMap)可以方便地为引擎/游戏提供单点配置,在其中选择和配置服务实现。

我的问题

  • 我经常觉得我是为了界面而创建界面
  • 我的生产力急剧下降,因为我所做的只是担心如何以 DI 方式做事,而不是快速简单地做事全局静态 way.
  • 在某些情况下,例如在运行时实例化新实体时,需要访问 IoC 容器/内核来创建实例。这会创建对 IoC 容器本身的依赖(SM中的ObjectFactory,Ninject中的内核实例),这确实违背了首先使用它的原因。如何解决这个问题?我想到了抽象工厂,但这只会使代码进一步复杂化。
  • 根据服务要求,某些类的构造函数可能会变得非常大,这将使该类在不使用 IoC 的其他上下文中完全无用。

基本上,进行 DI/IoC 会极大地降低我的工作效率,并且在某些情况下会进一步使代码和架构变得复杂。因此,我不确定这是我应该走的路,还是放弃并以老式的方式做事。我并不是在寻找一个单一的答案来说明我应该做什么或不应该做什么,而是讨论从长远来看使用 DI 是否值得,而不是使用全局静态/单例方式,我忽略了可能的优点和缺点,在处理 DI 时,我上面列出的问题的可能解决方案。


你应该回到老式的方式吗? 简而言之,我的回答是否定的。由于您提到的所有原因,DI 有很多好处。

我经常觉得我是为了界面而创建界面

如果您这样做,您可能违反了重用抽象原则 (RAP) http://www.codemanship.co.uk/parlezuml/blog/?postid=934

根据服务需求,某些类的构造函数可以获得 非常大,这将使该类在其他方面完全无用 未使用 IoC 的上下文。

如果您的类构造函数太大且复杂,这是向您表明您违反了另一个非常重要的原则的最佳方式:单一责任原则 https://en.wikipedia.org/wiki/Single_responsibility_principle。在这种情况下,是时候将代码提取并重构为不同的类,建议的依赖项数量约为 4 个。

为了进行 DI,您不必拥有接口,DI 只是将依赖项获取到对象中的方式。创建接口可能是能够替代依赖项以进行测试的必要方法。 除非依赖的对象是:

  1. 易于隔离
  2. 不与外部子系统(文件系统 ETC)

您可以将依赖项创建为抽象类,或者您想要替换的方法为虚拟方法的任何类。然而,接口确实创建了依赖关系的最佳解耦方式。

在某些情况下,例如在运行时实例化新实体时,一 需要访问 IoC 容器/内核才能创建实例。 这会创建对 IoC 容器本身(ObjectFactory 在 SM 中,Ninject 中的内核实例),这确实是 反对首先使用它的原因。怎么会这样 解决?我想到了抽象工厂,但这还只是更进一步 使代码变得复杂。

至于对 IOC 容器的依赖,您永远不应该在客户端类中依赖它。 他们不必这样做。

为了正确使用依赖注入,首先要理解依赖注入的概念成分根 http://blog.ploeh.dk/2011/07/28/CompositionRoot.aspx。这是唯一应该引用您的容器的地方。至此,整个对象图就构建完成了。一旦你理解了这一点,你就会意识到你的客户端永远不需要容器。因为每个客户端都只是注入其依赖项。

您还可以遵循许多其他创建模式来简化构建: 假设您要构造一个具有许多依赖项的对象,如下所示:

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();

您还可以使用 bad mans di 并创建一个完全不带参数的构造函数:

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();

当您的默认实现位于外部第三方库中时,使用 Poor Man 的 DI 被认为是不好的,但当您拥有良好的默认实现时,则不太坏。

显然还有所有 DI 容器、对象构建器和其他模式。

因此,您所需要做的就是为您的对象想出一个好的创建模式。你的对象本身不应该关心如何创建依赖关系,事实上它使它们变得更加复杂并导致它们混合了两种逻辑。所以我不认为使用 DI 会降低生产力。

在某些特殊情况下,您的对象不能只注入单个实例。生命周期通常较短且需要动态实例的情况。在这种情况下,您应该将 Factory 作为依赖项注入到对象中:

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 容器,我也使用了 activator,值得注意的是,工厂需要构造一个新的对象实例,而不仅仅是假设容器将提供一个新实例,因为该对象可能会注册不同的生命周期(单例) 、ThreadLocal 等)。但是,根据您使用的容器,有些容器可以为您生成这些工厂。但是,如果您确定该对象已使用 Transient 生命周期注册,则可以简单地解析它。

编辑:添加具有抽象工厂依赖项的类:

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 会极大地降低我的工作效率,并且 有些情况使代码和架构进一步复杂化

马克·西曼(Mark Seeman)就此主题写了一篇很棒的博客,并回答了这个问题: 我对这类问题的第一反应是:你说松散耦合的代码更难理解。比什么更难?

松耦合和大局 http://blog.ploeh.dk/2012/02/02/LooseCouplingAndTheBigPicture.aspx

编辑:最后我想指出,并非每个对象和依赖项都需要或应该进行依赖项注入,首先考虑您使用的内容是否实际上被视为依赖项:

什么是依赖关系?

  • 应用程序配置
  • 系统资源(时钟)
  • 第三方库
  • Database
  • WCF/网络服务
  • 外部系统(文件/电子邮件)

上述任何对象或协作者都可能超出您的控制范围,并导致副作用和行为差异,并使测试变得困难。现在是考虑抽象(类/接口)并使用 DI 的时候了。

什么不是依赖项,并不真正需要 DI?

  • List<T>
  • 内存流
  • 字符串/原语
  • 叶对象/Dto

诸如上述的对象可以在需要时使用以下方法简单地实例化new关键词。除非有特殊原因,否则我不建议对这样简单的对象使用 DI。考虑这个问题:该对象是否在您的完全控制之下,并且不会导致任何额外的对象图或行为副作用(至少是您想要更改/控制其行为或测试的任何内容)。在这种情况下,只需将它们更新即可。

我发布了很多 Mark Seeman 帖子的链接,但我真的建议您阅读他的书和博客文章。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

依赖注入和开发效率 的相关文章

  • 属性对象什么时候创建?

    由于属性实际上只是附加到程序集的元数据 这是否意味着属性对象仅根据请求创建 例如当您调用 GetCustomAttributes 时 或者它们是在创建对象时创建的 或者 前两个的组合 在由于 CLR 的属性扫描而创建对象时创建 从 CLR
  • 自动从 C# 代码进行调试过程并读取寄存器值

    我正在寻找一种方法来读取某个地址的 edx 注册表 就像这个问题中所问的那样 读取eax寄存器 https stackoverflow com questions 16490906 read eax register 虽然我的解决方案需要用
  • 模板类的不明确多重继承

    我有一个真实的情况 可以总结为以下示例 template lt typename ListenerType gt struct Notifier void add listener ListenerType struct TimeListe
  • C++ 求二维数组每一行的最大值

    我已经设法用这个找到我的二维数组的每一行的最小值 void findLowest int A Cm int n int m int min A 0 0 for int i 0 i lt n i for int j 0 j lt m j if
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • C# 用数组封送结构体

    假设我有一个类似于 public struct MyStruct public float a 我想用一些自定义数组大小实例化一个这样的结构 在本例中假设为 2 然后我将其封送到字节数组中 MyStruct s new MyStruct s
  • 当 Cortex-M3 出现硬故障时如何保留堆栈跟踪?

    使用以下设置 基于 Cortex M3 的 C gcc arm 交叉工具链 https launchpad net gcc arm embedded 使用 C 和 C FreeRtos 7 5 3 日食月神 Segger Jlink 与 J
  • 为什么模板不能位于外部“C”块内?

    这是一个后续问题一个答案 https stackoverflow com questions 4866433 is it possible to typedef a pointer to extern c function type wit
  • 在 ASP.Net Core 2.0 中导出到 Excel

    我曾经使用下面的代码在 ASP NET MVC 中将数据导出到 Excel Response AppendHeader content disposition attachment filename ExportedHtml xls Res
  • 初始化变量的不同方式

    在 C 中初始化变量有多种方法 int z 3 与 int 相同z 3 Is int z z 3 same as int z z 3 您可以使用 int z z 3 Or just int z 3 Or int z 3 Or int z i
  • Windows 10 中 Qt 桌面应用程序的缩放不当

    我正在为 Windows 10 编写一个简单的 Qt Widgets Gui 应用程序 我使用的是 Qt 5 6 0 beta 版本 我遇到的问题是它根本无法缩放到我的 Surfacebook 的屏幕上 这有点难以判断 因为 SO 缩放了图
  • 更改窗口的内容 (WPF)

    我创建了一个简单的 WPF 应用程序 它有两个 Windows 用户在第一个窗口中填写一些信息 然后单击 确定 这会将他们带到第二个窗口 这工作正常 但我试图将两个窗口合并到一个窗口中 这样只是内容发生了变化 我设法找到了这个更改窗口内容时
  • 用 C 实现 Unix shell:检查文件是否可执行

    我正在努力用 C 语言实现 Unix shell 目前正在处理相对路径的问题 特别是在输入命令时 现在 我每次都必须输入可执行文件的完整路径 而我宁愿简单地输入 ls 或 cat 我已经设法获取 PATH 环境变量 我的想法是在 字符处拆分
  • 什么是 C 语言的高效工作流程? - Makefile + bash脚本

    我正在开发我的第一个项目 该项目将跨越多个 C 文件 对于我的前几个练习程序 我只是在中编写了我的代码main c并使用编译gcc main c o main 当我学习时 这对我有用 现在 我正在独自开展一个更大的项目 我想继续自己进行编译
  • 在 URL 中发送之前对特殊字符进行百分比编码

    我需要传递特殊字符 如 等 Facebook Twitter 和此类社交网站的 URL 为此 我将这些字符替换为 URL 转义码 return valToEncode Replace 21 Replace 23 Replace 24 Rep
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • 如何在 C# 中播放在线资源中的 .mp3 文件?

    我的问题与此非常相似question https stackoverflow com questions 7556672 mp3 play from stream on c sharp 我有音乐网址 网址如http site com aud
  • 将变量分配给另一个变量,并将一个变量的更改反映到另一个变量中

    是否可以将一个变量分配给另一个变量 并且当您更改第二个变量时 更改会瀑布式下降到第一个变量 像这样 int a 0 int b a b 1 现在 b 和 a 都 1 我问这个问题的原因是因为我有 4 个要跟踪的对象 并且我使用名为 curr
  • 更改显示的 DPI 缩放大小使 Qt 应用程序的字体大小渲染得更大

    我使用 Qt 创建了一些 GUI 应用程序 我的 GUI 应用程序包含按钮和单选按钮等控件 当我运行应用程序时 按钮内的按钮和字体看起来正常 当我将显示器的 DPI 缩放大小从 100 更改为 150 或 200 时 无论分辨率如何 控件的
  • 如何将字符串“07:35”(HH:MM) 转换为 TimeSpan

    我想知道是否有办法将 24 小时时间格式的字符串转换为 TimeSpan 现在我有一种 旧时尚风格 string stringTime 07 35 string values stringTime Split TimeSpan ts new

随机推荐

  • 连接 pandas 中的列表 - 使用 PyCharm 发出警告

    这是一个最小的可重现示例 用于获取我不明白的警告 我的数据框 前 5 行 如下所示 10 列 每列都填充了一个字符串列表 Index HLA A1 D HLA A2 D HLA B1 D HLA B2 D HLA C1 D HLA C2 D
  • 在“获取源”步骤之后,TFS 保持不同步

    We migrated to TFS 2015 RTM recently and were successful in creating build pools configuring build agents and build defi
  • 创建静态库

    我正在尝试创建一个静态库以在我的 PHP 扩展中使用 为此 我正在编译我的 c文件使用gcc c file c o file o并获得 o文件 然后我用ar rcs lib a o将所有编译的对象归档到 a file 完成此操作后 我指的是
  • haskell负十进制数[重复]

    这个问题在这里已经有答案了 可能的重复 Haskell 中的负双精度数或浮点数 macports https stackoverflow com questions 4101599 negative doubles or floats in
  • 表单验证如何排除输入字段?

    我正在关注 bootstrap 4 表单验证https getbootstrap com docs 4 0 components forms validation https getbootstrap com docs 4 0 compon
  • 如何在 Context.MODE_PRIVATE 中创建嵌套文件夹和文件?

    我有一个要求 需要使用嵌套结构编写文件和文件夹Context MODE PRIVATE 我发现我们可以使用创建文件openFileOutput FILENAME Context MODE PRIVATE 并能够使用此方法创建文件 但后来我发
  • 如何从我的应用程序中启动 Mail.app 中的新消息窗口

    我可以使用命令启动 Mail app NSWorkspace共享工作空间 launchApplication Mail app 但我想在 Mail app 中启动新消息窗口 而不是整个 Mail app 我怎样才能这样做呢 我得到了答案 N
  • 从文件返回细节,python

    我有这段代码 我正在尝试计算以下内容的数量 py 脚本中的代码行 for loops 对于 while loops 同时 if 语句 如果 函数定义 def 乘号 除号 加号 减号 在数学符号上 代码可以工作 但是当代码寻找 if 语句时
  • 如何使用cmd/批处理文件删除目录中名为x的所有文件夹

    我有一个名为 x 的文件夹 其中包含许多子文件夹和文件 我想删除 x 中存在的名为 y 的文件夹及其所有子文件夹 必须删除的所述文件夹可能包含也可能不包含任何文件 我相信我可以使用 cmd 或某种批处理文件来完成此操作 但我是一个命令行新人
  • 在位图样式设计器中更改字体

    Delphi XE7 提供了 位图样式设计器 工具 工具 gt 位图样式设计器 可用于为您的 Metro 主题应用程序编辑和创建样式 更改按钮 复选框和标签的图形和颜色很有效 而且看起来很漂亮 但如何更改字体设置呢 更准确地说 我该怎么做才
  • 比亚恩会犯错误吗? (一边解释模板),还是我还是不明白?

    伙计们 我正在做 C 编程语言第三版 的练习 第 340 页有一个函数示例 template
  • 确定文件是否为空(SSIS)

    我正在尝试在 SSIS 2005 中开发一个包 我的过程的一部分是检查网络上的文件是否为空 如果不为空 则需要传递成功状态 否则 需要传递不成功状态 我想我需要一个脚本任务 但不知道如何去做 任何帮助表示赞赏 Create a connec
  • 保留 Emacs 中的窗口布局

    我已经以某种方式设置了我的窗口 如何保存此设置以供以后调用 我有时还是用C x r w
  • 验证本地 Laravel Homestead 服务器上的自签名证书

    我按照以下详细信息创建了 SSL 证书 因此我可以使用 https 通过 Laravel 的 Homestead 运行本地测试站点 在 homestead 虚拟机上添加 https 证书 https stackoverflow com qu
  • UITableViewController 背景图片

    如何设置图像UITableViewController 我使用了很多东西 但它不能帮助我将图像设置为背景 UIImageView bgView UIImageView alloc initWithImage UIImage imageNam
  • 在 r 绘图文本中指定小数位?

    我尝试格式化在基本图形系统中创建的回归曲线的标签 基本上 该标签从变量中提取斜率 截距和 r 方值 示例如下 plot rnorm 10 type n xlim c 0 100 ylim c 0 100 text x 0 y 100 adj
  • VBA许多按钮指向同一个_Click sub

    我的表单上有一堆文本框按钮对 单击按钮时 我想将文本框的值插入数据库 名称 文本框 和 按钮 遵循命名标准 例如 Value1Tb Value1Cmd 和 Value2Tb Value2Cmd 我的问题是 因为我想对每个按钮执行相同的操作
  • 哪个 Eclipse 可以与 ADT 完美配合?

    Eclipse 有很多版本 例如 靛蓝 朱诺 开普勒 月球 火星 其中哪一个最适合 ADT Stack Overflow 上有很多这样的问题 但都是 4 5 年前的问题 我正在寻找更新的东西 我提出你的问题是因为我自己也想知道这个问题 因为
  • 当一个子类没有额外属性时,教义表类继承

    我的映射有问题 我无法让它工作 我有一个像这样的抽象基类 Entity Table name actions InheritanceType JOINED DiscriminatorColumn name type type string
  • 依赖注入和开发效率

    Abstract 在过去的几个月里 我一直在编写一个轻量级 基于 C 的游戏引擎 具有 API 抽象和实体 组件 脚本系统 它的整体理念是通过提供类似于 Unity 引擎的架构来简化 XNA SlimDX 等游戏开发过程 设计挑战 正如大多