【问题标题】:Utility classes.. Good or Bad?实用程序类.. 好还是坏?
【发布时间】:2009-11-04 01:48:05
【问题描述】:

我一直在阅读,通过在代码中使用静态类/单例来创建依赖项,这种形式很糟糕,并且会产生问题,即。紧耦合和单元测试。

我有一种情况,我有一组 url 解析方法,它们没有与之关联的状态,并且只使用方法的输入参数执行操作。我相信你对这种方法很熟悉。

过去我会继续创建一个类并添加这些方法并直接从我的代码中调用它们。

UrlParser.ParseUrl(url);

但请稍等,这是对另一个类的依赖。我不确定这些“实用程序”类是否不好,因为它们是无状态的,这最大限度地减少了所述静态类和单例的一些问题。有人可以澄清一下吗?

我是否应该将方法移动到调用类,也就是说,如果只有调用类将使用该方法。这可能违反“单一责任原则”。

【问题讨论】:

  • "我有一种情况,我有一组没有状态关联的url解析方法,只使用方法的输入参数执行操作,相信你很熟悉一种方法。”这样的方法将被称为纯函数

标签: c# class static utility


【解决方案1】:

从理论设计的角度来看,我觉得实用程序类是应该尽可能避免的。它们基本上与静态类没有什么不同(虽然稍微好一点,因为它们没有状态)。

然而,从实际的角度来看,我确实创建了这些,并鼓励在适当的时候使用它们。试图避免使用实用程序类通常很麻烦,并且会导致代码的可维护性降低。不过,我确实尝试鼓励我的开发人员尽可能避免在公共 API 中使用这些。

例如,在您的情况下,我觉得 UrlParser.ParseUrl(...) 作为一个类可能会更好地处理。查看 BCL 中的 System.Uri - 它为统一资源标识符处理了一个干净、易于使用的界面,该界面运行良好,并保持实际状态。我更喜欢这种方法,而不是适用于字符串的实用方法,并强制用户传递字符串,记得验证它等等。

【讨论】:

  • 好点。 System.Uri 是将此类静态实用程序方法转换为对象的一个​​很好的示例。
  • 鉴于他们的例子,这也与他们的具体问题非常相关;)
  • 众所周知,System.Uri 存在与原始字符串表示和与绝对相对路径之间的转换相关的问题,这是一个非常糟糕的例子。这就是每一个抽象设计和 API 公式都会发生的事情,你永远不会开箱即用,而且对于 MSFT 框架设计人员来说,它需要很长时间才能正确完成,并且通常会导致比使用简单且已知的静态方法更多的复杂性具有功能性。但这通常是“框架”和“设计指南”狂热方法的问题..
  • 有人能解释一下“它们基本上与静态类没有什么不同(尽管稍微好一点,因为它们没有状态)”是什么意思。也许我错过了如何声明“实用程序”类。根据示例,它看起来像一个静态类。里德指的是什么“状态”?
  • 它通常只是一个没有字段或属性的静态类,因此没有保存“状态”,而只是一系列用作实用程序的方法。
【解决方案2】:

实用程序类没问题.....只要它们不违反设计原则。像使用核心框架类一样愉快地使用它们。

类应该命名良好且符合逻辑。实际上,它们并不是“实用程序”,而是原生类不提供的新兴框架的一部分。

使用诸如扩展方法之类的东西对于将功能与“正确”类对齐也很有用。但是,它们可能会引起一些混乱,因为扩展没有与它们通常扩展的类一起打包,这并不理想,但仍然非常有用并产生更清晰的代码。

【讨论】:

    【解决方案3】:

    您始终可以创建一个接口,并将其与实现该接口的类实例(而不是静态类)结合使用。

    问题变成了,真的值得付出努力吗?在某些系统中,答案是肯定的,但在其他系统中,尤其是较小的系统中,答案可能是否。

    【讨论】:

    • 这也是我的建议。如果实用程序方法复杂或计算量大,我建议使用依赖注入将其作为某种服务对象注入。否则我不会担心的。
    • 注入不会移除依赖。它只是让它更容易处理。
    • 删除依赖项的唯一方法是删除它。我不明白你的意思。
    【解决方案4】:

    我同意这里的其他一些回应,即经典的单例维护要避免的有状态对象的单个实例,而不一定是没有状态的实用程序类是邪恶的。我也同意 Reed 的观点,如果可能的话,将这些实用方法放在一个有意义的类中,并且在逻辑上人们会怀疑这些方法会驻留。我要补充的是,这些静态实用程序方法通常可能是扩展方法的良好候选者。

    【讨论】:

      【解决方案5】:

      我真的,真的想避开它们,但我们在开玩笑吧……它们潜入每一个系统。尽管如此,在给出的示例中,我将使用一个 URL 对象,然后该对象将公开 URL 的各种属性(协议、域、路径和查询字符串参数)。 几乎每次我想创建一个静态的实用程序类时,我都可以通过创建一个执行此类工作的对象来获得更多价值。

      以类似的方式,我创建了许多自定义控件,这些控件内置了对百分比、货币、电话号码等内容的验证。在此之前,我有一个 Parser 实用程序类,它包含所有这些规则,但它使只需在已经知道基本规则的页面上放置一个控件(因此只需要 业务逻辑 em> 要添加验证)。

      我仍然保留解析器实用程序类和这些控件隐藏该静态类,但广泛使用它(将所有解析保存在一个容易找到的地方)。在这方面,我认为拥有实用程序类是可以接受的,因为它允许我应用“不要重复自己”,而我可以从带有控件或使用实用程序的其他对象的实例类中受益。

      【讨论】:

        【解决方案6】:

        这实际上取决于上下文以及我们如何使用它。

        实用程序类本身还不错。但是,如果我们以不好的方式使用它,它会变得很糟糕。每种设计模式(尤其是单例模式)都可以轻松转化为反模式,实用程序类也是如此。

        在软件设计中,我们需要在灵活性和简单性之间取得平衡。如果我们要创建一个只负责字符串操作的 StringUtils:

        • 是否违反 SRP(单一职责原则)? -> 不,是开发人员将过多的职责置于实用程序类中违反了 SRP。
        • “它不能使用 DI 框架注入”-> StringUtils 实现会变化吗?我们要在运行时切换它的实现吗?我们要嘲笑它吗?当然不是。

        => 实用程序类本身还不错。是开发者的错让它变坏了。

        这完全取决于上下文。如果您只是要创建一个仅包含单一职责的实用程序类,并且仅在模块或层内私下使用。那你还是可以的。

        【讨论】:

          【解决方案7】:

          只要您设计好它们就可以了(也就是说,您不必不时更改它们的签名)。

          这些实用方法不会经常更改,因为它们只做一件事。当您想将一个更复杂的对象与另一个对象紧密结合时,问题就来了。如果其中一个需要更改或替换,如果您将它们高度耦合,则将更难。

          由于这些实用方法不会经常改变,我会说这不是什么大问题。

          我认为如果你一遍又一遍地复制/粘贴相同的实用程序方法,那将是最糟糕的。

          这个视频How to design a good API and why it matters 由 Joshua Bloch 制作,解释了在设计 API(这将是您的实用程序库)时要牢记的几个概念。尽管他是公认的 Java 架构师,但内容适用于所有编程语言。

          【讨论】:

            【解决方案8】:

            谨慎使用它们,您希望将尽可能多的逻辑放入您的类中,这样它们就不会只是数据容器。

            但是,与此同时,您也不能真正避免实用程序,它们有时是必需的。

            在这种情况下,我认为没关系。

            仅供参考,system.web.httputility 类包含许多您可能会发现有用的常见 http 实用程序。

            【讨论】:

              猜你喜欢
              • 2011-04-11
              • 1970-01-01
              • 1970-01-01
              • 2010-10-17
              • 1970-01-01
              • 2014-07-23
              • 1970-01-01
              • 1970-01-01
              • 2011-05-04
              相关资源
              最近更新 更多