IOC - 具有静态辅助方法的 util 类是不是应该与 IOC 连接?

Posted

技术标签:

【中文标题】IOC - 具有静态辅助方法的 util 类是不是应该与 IOC 连接?【英文标题】:IOC - Should util classes with static helper methods be wired up with IOC?IOC - 具有静态辅助方法的 util 类是否应该与 IOC 连接? 【发布时间】:2011-02-01 08:50:16 【问题描述】:

只是想继续了解 IOC 的原则。

Q1:静态方法 - 具有静态辅助方法的 util 类是否应该与 IOC 连接?

例如,如果我有一个带有许多静态方法的 HttpUtils 类,我是否应该尝试通过 IOC 将它传递给其他业务逻辑类?

以下问题可能是:

Q2:Singletons - 诸如日志之类的事情,您通常可以通过 Logger.getInstance() 类型调用访问它。您通常会保持原样,而不是使用 IOC 将记录器注入需要它的业务类吗?

Q3:静态类 - 我还没有真正使用过这个概念,但是如果您转向基于 IOC 的方法,您通常如何处理这个问题是否有任何指导方针。

提前致谢。

【问题讨论】:

【参考方案1】:

关于 IoC 的有趣之处在于,以样式编写的对象通常与这些细节解耦。

我们以实用程序类为例:

public class HttpUtils

    public static void DoStuff(int someValue)
    
        // ...
    

在非 IoC 应用程序中,您可以直接使用该方法:

public class Foo

    public int Value  get; set; 

    public void DoStuff()
    
        HttpUtils.DoStuff(Value);
    

但是,这会将DoStuff 的定义直接与其实现结合起来。 IoC 力求分离这些细节,因此我们自己定义操作:

public interface IDoesStuff

    void DoStuff(int someValue);

然后,我们在Foo 中留出空间以供更改实现:

public class Foo

    private readonly IDoesStuff _doesStuff;

    public Foo(IDoesStuff doesStuff)
    
        _doesStuff = doesStuff;
    

    public int Value  get; set; 

    public void DoStuff()
    
        _doesStuff.DoStuff(Value);
    

这将FooHttpUtils 分离。 DoStuff 概念的实现者现在是一个配置细节,而不是固有的依赖关系(因为它是静态方法)。

注意Foo 不知道它的IDoesStuff 是否是单例。该生命周期也是一个配置细节,而不是Foo的固有细节。

总而言之,IoC 和static 通常是矛盾的,因为 IoC 促进变革,而 static,顾名思义,阻止变革。在构造函数中声明依赖项,您会发现几乎从未使用过static 功能。

【讨论】:

感谢 Bryan - 这是否意味着 IOC 每次构建事物(例如记录器)可能会有更多开销?同样对于 utils 类型类,我想知道当您在辅助类中重构辅助函数时,这是否会使重构变得更加困难? (我自己正在使用 ReSharper) - 谢谢 最终你想消除实用程序类。这些类中的每一个上的每个方法都具有与上述HttpUtils.DoStuff 方法相同的耦合问题。因此,您不想要求 any 代码直接依赖于static 成员。相反,取HttpUtils.DoStuff 的主体,将其放在IDoesStuff 后面,并从HttpUtils 中完全删除该方法。现在,任何可能调用了静态方法的类都可以在其构造函数中接受IDoesStuff。 Viola:不再需要实用程序类! 我建议您按功能组织项目元素,而不是按技术细节。这意味着特定于 web 的实用程序类将与其他与 web 相关的东西一起使用,而特定于数据的实用程序类将与其他与数据相关的东西一起使用。仅仅因为它们是实用程序类而混合使用与 Web 和数据相关的实用程序类没有多大意义。因此,要回答您的问题,还有其他更有用的方法。 在某些情况下这是误导和不好的做法。考虑一些数学函数,例如如果 Math.cos 不是核心语言的一部分。然后,如果您实现一个类“Math”并将其注入到每个需要使用 Math.cos 的类中,您就意味着 Math 本身会随着阅读它的人而改变。不仅如此,您还生成了一堆无用的代码,以便在此过程中将依赖项放入类中。在这种情况下,Math 中的所有方法都应该是静态的,无需在容器中注册。 具体来说,我要解决的是这里的结论:“您会发现您几乎从未使用过静态功能”。虽然您不必使用静态功能,但在许多情况下最好这样做,因为它在语义上合适且不那么麻烦。 IMO 的“数学”类和“记录器”类在这方面是相似的。不应注入记录器,因为它们是单一的并且在每个类中都需要。注入它们会导致很多不必要的混乱。虽然 Math 没有依赖关系,但其他事情确实依赖于 Math。他们不需要注入数学。【参考方案2】:

IoC 容器通常用于注入具有状态的对象;或具有多个实现的类或接口,即使第二个实现是用于测试目的的模拟。如果这些都不正确,那么注入它您将一无所获。这些天最常见的习惯用法是让您的类前面有一个真实和模拟实现都可以实现的接口。

1) 辅助类上的静态方法 - 不,这些通常不会被 IoC 注入。通常它们是无状态实用程序。

举一个非常简单的例子,您不需要两个版本的实用方法StringUtils.Reverse()。您只需要一个,并且您可以轻松地围绕它编写测试,因为它没有状态或依赖项,因此模拟它绝对没有任何好处。示例测试:

string reversedString = StringUtils.Reverse("input");
Assert.AreEqual("tupni", reversedString)

如果实用程序不是真正无状态的(例如,依赖于 HttpContext.Current),那么您应该通过注入来明确依赖,而不是使实用程序静态。

2) 单例:通常是的,单例是注入的。但是关于 IoC 的一个好处是您不必担心是否只有其中之一。通过使用 IoC,您可以灵活地进行实例化。每次将特定类型作为单例或新实例的决定成为 IoC 容器配置的一部分,代码中没有其他任何内容需要更改。

因此,singleton-hood 不再是一个必须编码到类中的单独关注点(并且类在不需要时有多个关注点是不好的),它成为 IoC 容器的关注点。您不再使用诸如私有构造函数和public static GetInstance() 方法之类的特殊内容将类“作为单例”进行编码,您只需针对主要问题对其进行编码,而 IoC 容器的配置指定它是否为单例,或介于两者之间,例如每个线程一个实例。

3) 静态类 - 是静态方法的自然归宿。考虑在适当的情况下制作静态方法扩展方法。你不能注入这些类,因为你不能创建它们。使用静态类可以生成过程而不是面向对象的代码。这对于小型辅助方法来说并不是一件坏事,但如果大部分代码都是这样,那么您就没有使用 .Net 平台强大的 OO 功能。

【讨论】:

把静态方法变成扩展方法的好建议!【参考方案3】:

根据定义,静态方法不需要实例。 DI/IOC 旨在满足具有具体类的接口,并且鉴于您的静态类及其定义的静态方法不能实现接口或扩展类,这个问题没有任何意义。传递辅助类没有意义,因为不需要实例来使用静态方法。即使没有实例,您的代码也将始终执行相同的静态 helperr 方法。

在 IOC/DI 驱动的应用程序中,需要定义接口并至少有一个实现。这都是关于管理实例及其依赖关系的。

【讨论】:

【参考方案4】:

当实用程序类需要访问数据库时,就会出现困境。而db访问器需要Ioc,因此实用程序类必须使用Ioc,所以它不能是静态的。

但我真的希望实用程序类是静态的,以便于使用。我不想填充每个需要实用程序类的消费类的构造函数。

消费类本身甚至可能不需要数据库访问。所以我不想注入 db 访问器并将其传递给实用程序类。

估计目前还没有完美的解决方案。总有一天希望我们更进一步,除了构造函数/属性注入,还有“全局上下文注入/配置”,或者说“静态注入”,将Ioc应用到对象创建之外。

想一想,为什么不呢?

【讨论】:

这是一个答案,还是只是对问题的评论?

以上是关于IOC - 具有静态辅助方法的 util 类是不是应该与 IOC 连接?的主要内容,如果未能解决你的问题,请参考以下文章

私有的嵌套类(内部或静态)是不是可能具有具有公共访问权限的方法?

静态方法或单例性能方面(Android)?

碎Util/Utils 和 Helper 类的区别

Spring框架[一]——spring概念和ioc入门(ioc操作xml配置文件)

JUnit:仅使用静态方法测试助手类

Array类