【问题标题】:SOLID/TDD encourages one implementation for one interfaceSOLID/TDD 鼓励一个接口一个实现
【发布时间】:2012-08-27 17:03:31
【问题描述】:

我无意中听到以下内容,并被要求确认此声明:

“SOLID/TDD 鼓励一个接口一个实现,这不是现实世界,违背了接口的观点,不是吗?”

我最初同意 TDD 和 DI 的所有在线示例都遵循典型的 IRepository/MyRepository 示例,其中只有一个实现。再三考虑后我不同意。

我正在尝试做的是提供证明它不存在的证据,以及一个接口可以有多个实现的示例,并展示它在 DI 方面的工作原理。

我希望人们可以帮助我解决这个问题。

更新:虽然我了解 DI 和单元测试的概念,但我想展示的是我们如何在生产中让多个类实现一个接口。

UPDATE2: 想到了一个简单的例子,这里有多个实现的可能实现,但它仍然不能真正回答我想要的。如果您有一个对 ILogger 或 IDataProvider 或 ISomething 具有单一依赖关系的构造函数怎么办:

public interface ILogger
{
  void Write(string data);
}

public class FileLogger : ILogger
{
  void Write(string data)
  {
    //
  }
}

public class DBLogger : ILogger
{
  void Write(string data)
  {
    //
  }
}

public class EventViewerLogger : ILogger
{
  void Write(string data)
  {
    //
  }
}

public class Calculator
{
    private IEnumberable<ILogger> loggers;

    public Calculator(IEnumberable<ILogger> loggers)
    {
        this.loggers = loggers;
    }

    public int Add(int a, int b)
    {
      var result = a + b;

      foreach(var logger in loggers)
      {
        logger.Write("Result was " + logger);
      }
    }
}

【问题讨论】:

  • 相关:stackoverflow.com/questions/5411648/… 。孤立地进行单元测试时,您通常会得到一个接口,该接口由测试替身和精确的 one 生产类实现。
  • 对我来说这根本不是问题,我们创建的接口期望可能有多个实现...这就是让您的应用程序更灵活的原因...我真的不明白你为什么不想证明它...底线是即使您在 TDD/SOLID 中...使用接口的目的是指定一个合同并且可以由多个类实现...而不是试图证明这一点。 ..让要求您证明的人提高他/她的 OOP 技能...

标签: unit-testing design-patterns dependency-injection tdd solid-principles


【解决方案1】:

不,您可以拥有任意数量的实现。 TDD 测试实现,而不是接口。 DI 注入实现,而不是接口。 您使用接口,以便您可以有多个实现。

【讨论】:

  • 同意,我现在想向该声明的作者证明这一点
【解决方案2】:

在测试时,您通常会为接口创建一个模拟来隔离被测单元。

虽然你没有自己编写模拟,模拟仍然算作接口的实现对我来说。


我不知道你想在这里得到什么。如果您有多个相同接口的实现,这取决于您的问题。最后——没关系。接口的优点并不是因为您可以在生产中同时拥有多个实现。我们在项目中有很多接口,可能大多数只有一个实现。它仍然值得拥有,因为:

  • 调用代码并不关心你有多少实现 -> 它增强了解耦
  • 您今天只有一个实现,将来可能会有另一个。 (我知道,考虑到 YAGNI,这是一个弱论点)。
  • 测试有它的模拟实现
  • 改变实现就像创建另一个。您有多个实现,但不是同时实现。 => 它增强了可维护性

简而言之:您不需要在生产中进行很多实现来原谅使用接口。

【讨论】:

  • 是的,但它不在生产中。我想用 DI 在生产中演示一个接口的多个实现
  • 不要用不必要的接口使你的代码膨胀。见stackoverflow.com/questions/90851/…。特别是来自martinfowler.com/bliki/InterfaceImplementationPair.html 当你不打算有多个实现时使用接口是保持一切同步的额外努力。此外,它隐藏了你实际提供多个实现的情况。
  • @Michael:我部分同意。编写不必要的代码总是一件坏事。 “必要”总是取决于项目的规模和复杂程度。在我正在处理的大型且快速变化的项目中,我们节省了很多时间,因为在我们编写它时接口并不是一个明显的好处。在过去的几年里,我开始更多地使用接口。接口不应该膨胀任何东西。它们应该很小,使事情更清晰,并使软件的各个部分相互分离。
【解决方案3】:

SOLID 不促进接口和类之间的 1-1 映射。

接口隔离是最接近我们得到的接口。但它指出您应该创建尽可能具体的接口。 IUserRepository 可以分解为 IUserStorage (CRUD) 和 IUserQueries (搜索)。 UserRepository 可能会从一开始就实现两者,而您稍后将它们分解。这样做的好处是您可以轻松创建缓存实现(装饰器模式),或者使用不同的数据源进行写入和读取 (CQRS)。

如果您正确地遵循 SOLID,您最终会得到定义良好的小型类,这些类具有易于使用的接口并为其创建不同的实现。

问题在于大家在讨论 SOLID 时只是想到了典型的业务逻辑。但是我们都在不同的框架中运行代码,如果应用 SOLID,这将受益匪浅。

我不太擅长TDD,所以我不会评论它。

【讨论】:

    【解决方案4】:

    我同意 Stefan 的观点。测试可能不在生产环境中,但它们是您的应用程序的第一批消费者。

    关于 TDD/SOLID 产生无意义接口的论点,我要提出一个警告:SOLID 鼓励抽象,而不一定是接口。如果您正在为所有内容创建接口,那么这对某些开发人员来说可能显得非常笨拙。接口应该是系统消费者的扩展点。在某些情况下,如果测试是您的主要目标,那么具有虚拟方法的抽象类或非密封类可能更合适。一些开发人员将内部类上的虚拟方法视为永远不会在其应用程序范围之外使用的东西。这种说法可能有好处,但总有可能将其扩展到外部消费者——将其分解成小块可能会使扩展和重构更容易。还有一点要说的是不必在几个小时内手动测试应用程序。这是你的同事应该愿意做出的妥协。

    我开发的应用程序很少,其中核心服务需要每个实例有更多的实现,但我开发的应用程序需要不同的实现来满足特定于硬件的实现,例如 网络摄像头,Kinect。我可能一次只需要一个,但混合/匹配组件的能力非常强大。

    关于一次多个实现,策略插件模式很流行。可以像完全通过插件组成其界面的模块化应用程序一样广泛,也可以是配置驱动的税收计算。

    根据您的示例,日志记录通常是混合实现的一个很好的示例。我经常使用 log4net 将所有内容记录到文件中,将错误记录到事件日志中,并将崩溃记录到电子邮件附加程序。

    【讨论】:

    • 您已经开始同意Stefan的观点,但随后解释说他错了,没有必要过度使用接口,因为您可以使用虚拟方法进行模拟。您的回答自相矛盾,我建议您删除第一句话。
    • 再读一遍。我从来没有说过斯特凡是错的。 Stefan 的立场是,这些接口并不是无用的,因为它们用于测试。我的立场很相似,测试是你代码的第一批消费者。在 Stefan 的立场之上,我的观点是 SOLID 鼓励抽象(不是特别是 C# 接口)并且抽象是好的。
    • 恕我直言,Stefan 的立场是应该始终使用接口,即使使用虚拟方法会更简单。
    • Stefan 在哪里说 ALWAYS?
    【解决方案5】:

    所以您正在寻找具有多个实现并用于生产环境的接口示例?

    JDBC - Oracle、MySql、任何其他数据库都有自己的 JDBC 接口实现

    JPA - 请参阅JPA Implementations - Which one is the best to use? 了解接口的实现列表

    或者您正在寻找其他东西?

    【讨论】:

    • 只是一个简单的演示。也许 ILogger 与 FileLogger、DBLogger、EventViewerLogger。但是不确定如何在一个应用中使用所有 3 个
    • @Jon 只是将所有内容记录到一个文件中(即使在无法访问数据库时也可以使用),但将严重错误记录到数据库中以便于分析并将所有错误记录到 eventviewerlog 中,因为这将是最简单的选项让监控解决方案监控您的系统...
    【解决方案6】:

    Bob 叔叔,坚实原则的命名者(不是发明者)有一个著名的会计系统示例 您可以在《原则、模式和实践》一书中找到它。 在这个例子中,他有一个类可以根据员工列表生成报告。雇员可以是按小时付费的雇员,或者例如是按月付费的雇员。尽管如此,他们都是雇员,因此实现了相同的接口。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-16
      • 1970-01-01
      • 1970-01-01
      • 2016-11-23
      • 2014-11-18
      • 1970-01-01
      • 2011-04-24
      相关资源
      最近更新 更多