【问题标题】:Am I confused about interfaces?我对接口感到困惑吗?
【发布时间】:2011-12-09 14:44:19
【问题描述】:

我正在维护一个 ASP.NET MVC 项目。在该项目中,原始开发人员拥有大量的接口。例如:IOrderServiceIPaymentServiceIEmailServiceIResourceService。我感到困惑的是,它们中的每一个都只由一个类实现。换句话说:

OrderService : IOrderService
PaymentService : IPaymentService

我对接口的理解一直是它们被用来创建一个组件可以轻松互换的架构。比如:

Square : IShape
Circle : IShape

此外,我不明白这些是如何创建和使用的。这是 OrderService:

public class OrderService : IOrderService
{
    private readonly ICommunicationService _communicationService;
    private readonly ILogger _logger;
    private readonly IRepository<Product> _productRepository;

    public OrderService(ICommunicationService communicationService, ILogger logger,
        IRepository<Product> productRepository)
    {
        _communicationService = communicationService;
        _logger = logger;
        _productRepository = productRepository;
    }
}

这些对象似乎永远不会直接创建,因为在OrderService orderService = new OrderService() 中它总是使用接口。我不明白为什么使用接口而不是实现接口的类,或者它是如何工作的。关于我的谷歌技能没有发现的界面,我是否缺少一些重要的东西?

【问题讨论】:

  • 也许这个想法是使用接口,以便业务对象可以用简单的单元测试代替(但从来没有那么远)?
  • 如果一个接口只由一个类实现,这似乎有点矫枉过正。也许这是为一个从未到来的未来而设计?
  • 通过说“我感到困惑的是这些中的每一个都仅由单个类实现”您指的是:“公共类 OrderService : IOrderService”吗?如果是这样,项目中是否有很多地方构造函数有Ilogger或其他接口作为参数?
  • @ChrisW 是的,它在项目中的任何地方都这样做
  • 那么他似乎在做一件很正常的事情,使用 IoC 容器进行构造函数注入。如果您检查 global.asax,您可能会找到容器的设置代码和绑定代码。

标签: c# interface


【解决方案1】:

这种特殊的设计模式通常是为了方便单元测试,因为您现在可以将 OrderService 替换为 TestOrderService,两者都仅作为 IOrderService 引用。这意味着您可以编写 TestOrderService 来为被测类提供特定行为,然后感知被测类是否在做正确的事情。

在实践中,上述工作通常是通过使用 Mocking 框架来完成的,因此您实际上不必手动编写 TestOrderService,而是使用更简洁的语法来描述它在典型测试中的行为方式,然后有模拟框架为您动态生成一个实现。

至于为什么您从未在代码中看到“new OrderService”,很可能您的项目正在使用某种形式的控制反转容器,它有助于自动依赖注入。换句话说,您不必直接构造 OrderService,因为在某处您已配置 IOrderService 的任何使用都应通过构造单例 OrderService 并将其传递给构造函数来自动完成。这里有很多微妙之处,我不确定你的依赖注入是如何完成的(它不一定是自动的;你也可以手动构造实例并通过构造函数传递它们。)

【讨论】:

  • 它特别适用于那些过于贪婪而无法购买合适的单元测试框架的人——即主要是一个合适的模拟服务,可以模拟非接口或非虚拟方法。一旦你得到一个合适的,你实际上可以编写既安全又好的设计类以及好的单元测试。以及模拟静态方法调用和构造。
  • @TomTom,仅通过定义明确的接口访问服务有什么不安全的?
  • 没什么,但是模拟框架中的单元测试通常要求方法是虚拟的,而正常的 OOP 实践需要一个密封的类。因此,必须编写糟糕的类来支持模拟框架。或者一种在不需要时实现不需要的接口。我有大量的内部 IOC,并且认为不需要有一个接口只是为了能够对另一个类进行单元测试,我可以在需要时对类进行 pss 并模拟 SEALED 类。
  • 例如,制作一个 SEALED OrderService,我仍然可以模拟它。哎呀,我可以模拟静态“实例”方法,用我的模拟实例替换返回的值;)
  • @TomTom,我绝对推荐密封服务类,这就是接口非常适合封装服务的原因(因为密封类可以实现接口。)它们很容易维护,任何半途而废重构工具,所以真正唯一的反对意见是它在源代码中添加了更多的文件和文本行。我个人觉得这是一个合理的权衡。静态迂回工具非常强大,但是使用它们来模拟可以很容易地封装为接口的服务,这让我感觉就像使用大锤杀死苍蝇一样。
【解决方案2】:

这不是接口的唯一用途,在 MVC 中,它们被用于将合同与实现分离。要了解 MVC,您需要阅读一些相关主题,例如关注点分离和控制反转 (IoC)。创建要传递给 OrderService 构造函数的对象的实际行为由 IoC 容器基于一些预定义的映射。

【讨论】:

    【解决方案3】:

    这些对象似乎永远不会像在 OrderService orderService = new 中那样直接创建 订单服务()

    所以呢?

    关键是有人调用了 OrderService 构造函数,而调用者负责创建它们。他把它们递了过去。

    我不明白为什么使用接口而不是实现 界面

    因为你不想知道这个类——它可能会改变,是外部的,可以使用 IOC 容器进行配置,而程序员决定甚至不需要一个通用的基类。您对某人如何实现使用的实用程序类做出的假设越少越好。

    对于我的谷歌技能所没有的界面,我是否缺少一些重要的东西 揭穿?

    不,一本关于 OO 编程的好书比随机的 google sn-ps 更有帮助。这基本上属于架构领域和 .NET 基础知识(第一部分)。

    【讨论】:

    • 鉴于上下文我认为接口在这里被滥用,可在 YAGNI 下归档。每次,原作者都不是简单地创建一个类,而是创建一个接口,然后在一个类中实现它。如果你问我,这有点臃肿:大多数类可能永远不会受制于另一个实现。
    • 我不太确定,查看代码 sn-p 它只是具有接口的服务、记录器和代表 - 这意味着(对我而言)正在使用 IoC 容器。用户关于在这个项目中使用接口的问题(Square : IShape 位)再次暗示该项目仅使用可注入组件的接口。 但是这是很多“暗示”,如果不看项目,就无法更好地了解正在发生的事情。
    【解决方案4】:

    针对接口而不是对象进行编程是一种很好的做法。 This 问题给出了很好的理由,但一些原因包括允许更改实现(例如用于测试)。

    仅仅因为目前只有 1 个类实现了该接口,并不意味着它将来不能改变。

    此外,我不明白这些是如何创建和使用的。

    这称为dependency injection,基本上意味着该类不需要知道如何或从何处实例化它的依赖项,其他人会处理它。

    【讨论】:

      【解决方案5】:

      这些是服务接口,封装了某种外部性。您通常在主项目中只有一个实现,但您的测试使用不依赖于外部内容的更简单的实现。

      例如,如果您的支付服务与 paypal 联系以验证付款,您不希望在测试无关代码时这样做。相反,您可以将它们替换为始终返回“付款成功”并检查订单流程是否通过的简单实现,以及返回“付款失败”并检查订单流程是否也失败的另一个实现。

      为避免依赖于实现,您不要自己创建实例,而是在构造函数中接受它们。然后创建您的类的 IoC 容器将填充它们。查找 Inversion of Control

      您的项目可能有一些代码在其启动代码中设置 IoC 容器。并且该代码包含有关当您想要实现某个接口时要创建哪个类的信息。

      【讨论】:

        猜你喜欢
        • 2019-04-12
        • 2011-07-05
        • 1970-01-01
        • 2016-11-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-14
        相关资源
        最近更新 更多