如何向静态类注入依赖

2023-12-03

在我的应用程序中,我经常想将日志消息写入磁盘。我创建了一个简单的记录器类,它是使用依赖注入构建的,如下所示:

var logger = new LogService(new FileLogger());
logger.WriteLine("message");

但这现在意味着我系统中需要记录的每个类都需要得到这个LogService注入其中,这似乎是多余的。因此,我喜欢使LogService相反,静态。这样就不需要将其注入到使用类中。

但问题来了。如果我将记录器类设置为静态,则无法通过构造函数将依赖项注入到该静态类中。

所以,我改变了我的LogService对此。

public static class LogService()
{
    private static readonly ILoggable _logger;
    static LogService()
    {
         _logger = new FileLogger();
    }
    
    public static void WriteLine(string message) ...
}

这对我来说感觉很奇怪。我认为这不再是 DI 了。

将依赖项注入静态类的最佳方法是什么?


依赖注入作为一种实践,旨在引入抽象(或seams)来解耦不稳定的依赖关系。易失性依赖项是一个类或模块,其中可以包含不确定性行为,或者通常是您希望能够替换或拦截的内容。

有关易失性依赖项的更详细讨论,请参阅本免费阅读简介的第 1.3.2 节 of my book.

因为你的FileLogger写入磁盘,其中包含不确定性行为。为此,您介绍了ILoggable抽象。这使得消费者能够从FileLogger实现,并允许您 - 稍后 - 当出现此类要求时,轻松地交换它FileLogger实施与SqlLogger记录到 SQL 数据库的实现,或者甚至有一个将调用转发到两个数据库的实现FileLogger or the SqlLogger.

然而,为了能够成功地将消费者与其不稳定的依赖关系解耦,您需要inject对消费者的依赖。有以下三种常见模式可供选择:

  • 构造函数注入- 依赖关系被静态定义为类的参数列表instance构造函数。
  • 房产注入- 依赖项通过可写注入到消费者中instance特性。这种模式有时也称为“setter 注入”,尤其是在 Java 世界中。
  • 方法注入- 依赖项作为方法参数注入到消费者中。

构造函数注入和属性注入都适用inside应用程序的启动路径(也称为成分根)并要求使用者将依赖项存储在私有字段中以供以后重用。这要求构造函数和属性是实例成员,即非静态。构造函数注入通常优于属性注入,因为属性注入会导致时间耦合。静态构造函数不能有任何参数,静态属性会导致环境上下文反模式(参见第 5.3 节)——这会妨碍可测试性和可维护性。

另一方面,应用方法注入outside组合根,它确实not存储任何提供的依赖项,但仅使用它。这是一个来自早期参考:

// This method calculates the discount based on the logged in user.
// The IUserContext dependency is injected using Method Injection.
public static decimal CalculateDiscountPrice(decimal price, IUserContext context)
{
    // Note that IUserContext is never stored - only used.
    if (context == null) throw new ArgumentNullException("context");
    decimal discount = context.IsInRole(Role.PreferredCustomer) ? .95m : 1;
    return price * discount;
}

因此,方法注入是三种模式中唯一可以同时应用于实例和静态类的模式。

当应用方法注入时,该方法的consumer必须提供依赖项。然而,这确实意味着消费者本身必须通过构造函数、属性或方法注入提供该依赖项。例如:

public class ProductServices : IProductServices
{
    private readonly IProductRepository repository;
    private readonly IUserContext userContext;

    public ProductServices(
        IProductRepository repository,
        IUserContext userContext) // <-- Dependency applied using Ctor Injection
    {
        this.repository = repository;
        this.userContext = userContext;
    }

    public decimal CalculateCustomerProductPrice(Guid productId)
    {
        var product = this.repository.GetById(productId);

        return CalculationHelpers.CalculateDiscountPrice(
            product.Price,
            this.userContext); // <-- Dep forwarded using Method Injection
    }
}

你的静态例子LogService创造了FileLogger它的构造函数内部是紧密耦合代码的一个很好的例子。这被称为控制狂反模式(第 5.1 节)或者一般可以看作是DIP 违规——这是opposite of DI.

为了防止易失依赖项的紧密耦合,最好的方法是LogService非静态并将其易失性依赖项注入到其唯一的公共构造函数中:

public class LogService
{
    private readonly ILoggable _logger;

    public LogService(ILoggable logger)
    {
         _logger = logger;
    }
    
    public void WriteLine(string message) ...
}

这可能会违背你的目的LogService阶级,因为现在消费者通过注射会过得更好ILoggable直接代替注入LogService。但这让您回到了为什么您可能首先想要将该类设为静态的原因,即您有很多需要记录的类,并且注入起来感觉很麻烦ILoggable到所有这些构造函数中。

但是,这可能是由代码中的另一个设计问题引起的。要理解这一点,您可能需要阅读这个问答了解可以进行哪些设计更改以允许更少的类依赖于您的记录器类。

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

如何向静态类注入依赖 的相关文章

  • 如何检查QProcess是否正确执行?

    QProcess process sdcompare QString command sdcompare QStringList args sdcompare command sdcompare diff args sdcompare lt
  • 向 Nhibernate 发出 SQL 查询

    如何将此 SQL 查询发送给 Nhibernate SELECT Customer name FROM Company INNER JOIN Customer ON Company CompanyId Customer CompanyId
  • 互斥体实现可以互换(独立于线程实现)

    所有互斥体实现最终都会调用相同的基本系统 硬件调用吗 这意味着它们可以互换吗 具体来说 如果我使用 gnu parallel算法 使用openmp 并且我想让他们称之为线程安全的类我可以使用boost mutex用于锁定 或者我必须编写自己
  • 单元测试一起运行时失败,单独运行时通过

    所以我的单元测试遇到了一些问题 我不能只是将它们复制并粘贴到这里 但我会尽力而为 问题似乎是 如果我一项一项地运行测试 一切都会按预期进行 但如果我告诉它一起运行测试 则 1 5 将通过 TestMethod public void Obj
  • 如何从 .resx 文件条目获取注释

    资源文件中的字符串有名称 值和注释 The ResXResourceReader类让我可以访问名称和值 有办法看评论吗 你应该能够得到Comment via ResXDataNode class http msdn microsoft co
  • C# Dns.GetHostEntry 不返回连接到 WiFi 的移动设备的名称

    我有一个 C 中的 Windows 窗体应用程序 我试图获取列表中所有客户端的主机名 下面给出的是 ra00l 来自此链接的代码示例 GetHostEntry 非常慢 https stackoverflow com questions 99
  • 无法在 Windows 运行时组件库的 UserControl 中创建依赖项属性

    我想在用户控件内创建数据可绑定属性 这个用户控件包含一个 Windows 运行时组件 项目 我使用下面的代码来创建属性 public MyItem CurrentItem get return MyItem GetValue Current
  • Rx 中是否有与 Task.ContinueWith 运算符等效的操作?

    Rx 中是否有与 Task ContinueWith 运算符等效的操作 我正在将 Rx 与 Silverlight 一起使用 我正在使用 FromAsyncPattern 方法进行两个 Web 服务调用 并且我想这样做同步地 var o1
  • 在一个字节中存储 4 个不同的值

    我有一个任务要做 但我不知道从哪里开始 我不期待也绝对不想要代码中的答案 我想要一些关于该怎么做的指导 因为我感到有点失落 将变量打包和解包到一个字节中 您需要在一个字节中存储 4 个不同的值 这些值为 NAME RANGE BITS en
  • 如何使用 watin 中的 FileUploadDialogHandler 访问文件上传对话框

    我正在使用 IE8 和 watin 并尝试通过我的网页测试上传文件 我不能简单地使用 set 方法设置上传文件 例如 ie FileUpload Find ById someId Set C Desktop image jpg 因为上传文本
  • 上下文敏感与歧义

    我对上下文敏感性和歧义如何相互影响感到困惑 我认为正确的是 歧义 歧义语法会导致使用左推导或右推导构建多个解析树 所有可能的语法都是二义性的语言是二义性语言 例如 C 是一种不明确的语言 因为 x y 总是可以表示两个不同的事物 如下所述
  • (de)从 CSV 序列化为对象(或者最好是类型对象的列表)

    我是一名 C 程序员 试图学习 C 似乎有一些内置的对象序列化 但我在这里有点不知所措 我被要求将测试数据从 CSV 文件加载到对象集合中 CSV 比 xml 更受青睐 因为它更简单且更易于人类阅读 我们正在创建测试数据来运行单元测试 该集
  • gcc 的配置选项如何确定默认枚举大小(短或非短)?

    我尝试了一些 gcc 编译器来查看默认枚举大小是否很短 至少一个字节 强制使用 fshort enums 或无短 至少 4 个字节 强制使用 fno short enums user host echo Static assert 4 si
  • 用于 C# 的 TripleDES IV?

    所以当我说这样的话 TripleDES tripledes TripleDES Create Rfc2898DeriveBytes pdb new Rfc2898DeriveBytes password plain tripledes Ke
  • Process.Start() 方法在什么情况下返回 false?

    From MSDN https msdn microsoft com en us library e8zac0ca v vs 110 aspx 返回值 true 表示有新的进程资源 开始了 如果由 FileName 成员指定的进程资源 St
  • 有没有办法强制显示工具提示?

    我有一个验证字段的方法 如果无法验证 该字段将被清除并标记为红色 我还希望在框上方弹出一个工具提示 并向用户显示该值无效的消息 有没有办法做到这一点 并且可以控制工具提示显示的时间 我怎样才能让它自己弹出而不是鼠标悬停时弹出 If the
  • 线程和 fork()。我该如何处理呢? [复制]

    这个问题在这里已经有答案了 可能的重复 多线程程序中的fork https stackoverflow com questions 1235516 fork in multi threaded program 如果我有一个使用 fork 的
  • 检查Windows控制台中是否按下了键[重复]

    这个问题在这里已经有答案了 可能的重复 C 控制台键盘事件 https stackoverflow com questions 2067893 c console keyboard events 我希望 Windows 控制台程序在按下某个
  • 防止在工厂方法之外实例化对象

    假设我有一个带有工厂方法的类 class A public static A newA Some code logging return new A 是否可以使用 a 来阻止此类对象的实例化new 那么工厂方法是创建对象实例的唯一方法吗 当
  • 在客户端系统中安装后桌面应用程序无法打开

    我目前正在使用 Visual Studio 2017 和 4 6 1 net 框架 我为桌面应用程序创建了安装文件 安装程序在我的系统中完美安装并运行 问题是安装程序在其他计算机上成功安装 但应用程序无法打开 edit 在客户端系统中下载了

随机推荐

  • Boost Regex 与 Snow Leopard 配合不佳

    所以我继承了用 C 编写的使用 Boost 库的代码 我可以在 Linux Ubuntu 上编译 使用代码块 并运行代码 但是当我将其移植到 mac 并安装 boost 库时 我可以使用代码块编译它 并指定正则表达式库的位置 但它赢了别跑
  • 泛型方法:使用参数实例化泛型类型[重复]

    这个问题在这里已经有答案了 我有一个接受类型 T 的通用方法 我需要能够调用需要单个 XmlNode 的构造函数 目前 我正在尝试通过拥有一个具有我想要的构造函数的抽象基类 加上一个无参数的构造函数 这样除了添加实际的子类之外我不必编辑 子
  • WPF MVVM 导航视图

    我有一个具有多个视图的 WPF 应用程序 我想从视图 1 切换到视图 2 然后我可以切换到多个视图 所以我想在视图 1 上有一个按钮 在同一窗口中加载视图 2 我尝试了这些东西 但无法让它发挥作用 如何使用 MVVM Light for W
  • 如何将“文本框”中的文本添加到图像中?

    我正在开发一种工具 可以将文本添加到图像的特定区域 例如在对话气泡内 我正在生成一个边界框 并希望将文本限制为仅位于该边界框内 但到目前为止 查看 cv2 和 PIL 库 它们似乎只采用文本的起点 而不是边界框 cv2 import cv2
  • 尝试从广播接收器启动活动

    我正在尝试创建一个锁屏 当我尝试启动时com fira locker LockScreenActivity从广播接收器 我刚刚收到一个错误 错误说 java lang NullPointerException Attempt to invo
  • 为什么我不能将逆变接口作为接口上方法的参数?

    我正在尝试使用接口设置 CoR 其中链中的处理程序可以是使用逆变的派生较少的事件类型 我创建这个界面来做到这一点 public interface IHandler
  • :counter_cache 总项目数

    我有一组简单的两个相关的 订单 表 其中有许多 line items 还有与行项目关联的数量 例如 Order1line item a 初学者编篮子 数量 3line item b 吸血鬼傻瓜指南 数量 1 当我建立迁移时 我可以使用以下方
  • 使用 API 响应填充联系表单 7 发布的数据

    我正在尝试填充 posted data dynamichidden 458 与 PHP 变量 body body 是一个 API 响应 我想将其添加到数据中 然后将其发送到数据层以便稍后捕获 下面是它如何发送和接收 API 信息的示例代码
  • 绕过Java中的多重继承

    我认为我的继承问题有解决方案 但我找不到 我正在开发一个 Android 应用程序 目标是 Android 2 1 它重用SlidingDrawer 对于我的菜单 大部分页面上 为了避免在所有 Activity 上初始化它 我创建了一个De
  • 为什么我们不能将数组的地址分配给指针?

    int q 10 0 cout lt lt q lt lt endl cout lt lt q lt lt endl cout lt lt q 0 lt lt endl 输出是 0x7fffd4d2f860 0x7fffd4d2f860 0
  • 我如何知道我的 WebView 已加载 100%?

    我正在尝试在 WebView 中加载一些包含 JavaScript 的 HTML 代码 现在 我想测试我的 WebView 是否在 5 秒之前加载 我已经尝试过该方法getProgress 但有时我得到进度是100 但我的Webview没有
  • 测试回文的各种方法的性能 [Python]

    今天 我正在解决几个编程难题 面对测试字符串以确定它是否是回文的任务 我想出了几种方法来完成此任务 下面描述了这三种方法的基础知识 省略了大部分整理和测试代码 def check palin victim method if method
  • 通过绑定更改为“最大”时进度条未更新

  • 未调用地理围栏 didEnterRegion 和 didExitRegion 方法?

    大家好 我正在研究地理围栏 位置管理器 didDetermineState 正在正确调用 但是当我进入区域时 didEnterRegion 和 didExitRegion 从未被调用 这是我的代码 ViewController m Geof
  • iOS:如何让用户在 SwiftUI 应用程序中共享来自 Safari/Chrome 的 URL

    我想在我的应用程序中添加一项功能 浏览网页的用户可以按链接上的 共享 当正常的应用程序出现时 如 消息 邮件 Instagram 等 他们可以看到我的应用程序 当他们点击它时 我想处理我的应用程序内的网址 我尝试过搜索 swiftui 添加
  • Git --force-with-lease 在分支中带有 + (refspec)

    在当前的 Git 上 两者之间是否存在实质性差异git push force with lease origin somebranch git push force with lease origin somebranch and git
  • CheckedListBox C# 的 RepeatDirection 属性

    所以我在这里很困惑 我想让我的多线CheckedListBox进行水平排序 我做了一些研究 这一切都导致了RepeatDirection财产 MSDN 中的示例 但我没有网页 也不知道任何 XML 我不能以某种方式使用这个属性吗Checke
  • 带有监听器的广播接收器在不使用时会耗尽电池电量

    我有一个等待的接收器TelephonyManager ACTION PHONE STATE CHANGED public void onReceive Context context Intent intent String theActi
  • 从数据框中删除某些值为 NA 的列

    我有一个数据框 其中一些值不适用 我想删除这些列 我的 data frame 看起来像这样 v1 v2 1 1 NA 2 1 1 3 2 2 4 1 1 5 2 2 6 1 NA 我尝试估计列平均值并选择列平均值 NA 我尝试了这个声明 它
  • 如何向静态类注入依赖

    在我的应用程序中 我经常想将日志消息写入磁盘 我创建了一个简单的记录器类 它是使用依赖注入构建的 如下所示 var logger new LogService new FileLogger logger WriteLine message