【问题标题】:What is the "Thin line" between thin and fat interfaces?瘦接口和胖接口之间的“细线”是什么?
【发布时间】:2011-12-12 21:37:06
【问题描述】:

我有一个预订系统,可让您预订、修改现有预订和取消现有预订。我正在研究接口隔离原则,我想知道我应该使我的接口有多薄,以及我是否违反了单一责任原则。我最初的设计是:

interface IReservation
{
     void Book();
     void Modify();
     void Cancel(); 
}

但后来我想,如果一个预订系统不需要为预订实现这些方法之一并且只关注预订,例如,我做了以下操作:

interface IBook
{
     void Book();
}


interface IModify
{
    void Modify();
}

interface ICancel
{
    void Cancel();
}

现在我可以这样做了:

interface IReservation : IBooking
{


}

interface IReservation : IBooking, IModify
{


}

所以问题就变成了我是否将它像这样细化了。此外,为接口考虑名称变得更加困难,例如,我不喜欢 IModify 或 ICancel (它们对我来说就像应该在 IReservation 接口上的方法)。你如何确定什么应该进入一个接口,什么应该被隔离到另一个接口、类等等......

【问题讨论】:

  • 很难判断你是否会走得更远。如果你从字面上看这个例子 - 是的。 IMO 在这种情况下(小)由 YAGNI 原则决定,但如果它变得更大,那么其他因素变得更加重要。
  • 不想用我有限的回答破坏你的问题,但我可以对这两种方法说些什么。第一个是最直观的,每个人都会从它开始。第二个更像是框架级接口,如 IEnumerable 等。这是一次性工作还是尝试创建框架?

标签: .net oop interface solid-principles


【解决方案1】:

我建议有两个接口

interface IBookableReservation
{
     void Book();
}

as suggestedAdam Robinson

interface IModifiableReservation
{
   void Modify();
   void Cancel();
}

您无需创建 IReservation 接口,而是直接从 IBookableReservation 和 IModifiableReservation 继承您的类。客户端可以使用一个或两个接口。

创建一个接口是没有意义的,它只是复制单个类的公共方法。 如果 interface 与仅带有“I”前缀的类具有相同的名称,则这是代码异味,因为它表明它是 1:1 relationship between the interface and the concrete classes that implementit。

Reused Abstractions Principle (RAP)

来自http://martinfowler.com/bliki/InterfaceImplementationPair.html

当您不打算拥有多个接口时使用接口 实现是保持一切同步的额外努力。此外 它隐藏了您实际提供多个的情况 实现。

【讨论】:

  • Foo 可以实现IFoo 接口,因为有一个FakeFoo 类用作模拟。这根本不一定是代码异味。
  • 我们正在针对一个逻辑接口(而不是关键字接口)进行测试,无论该逻辑接口是由类还是接口提供都没有关系。大多数 movking 框架都支持模拟虚拟方法。不要用实际上可能不需要的接口来膨胀你的代码库。见stackoverflow.com/questions/12174304/…
【解决方案2】:

如果您的应用程序确实需要支持不同类型的预订,并且稍后一些通用逻辑应该能够处理所有这些 - 我建议为每种服务类型引入单独的接口,并为每个预订本身引入单个接口,这个想法 - 预订提供集合服务,因此您可以只公开由公共接口IReservationService 抽象的服务列表,并摆脱每个预订系统的多个接口实现。只需为每个服务创建一个类并通过 ctor of Reservation 注册服务:

var reservationWithBooking = 
     new Reservation(new List<IReservationService { new BookingService() });

var reservationWithCancellation = 
     new Reservation(new List<IReservationService { new CancellationService(); });

var mixedReservation = 
     new Reservation(new List<IReservationService 
                            {
                                new BookingService(),
                                new CancellationService()
                            });

接口:

interface IReservationService
{       
}

interface IBookingService : IReservationService
{
   void Book(...);
}

interface ICancellationService : IReservationService
{
   void Cancel(...);
}

interface IReservation
{
   IEnumerable<IReservationService> Services { get; }
}

class Reservation : IReservation
{
    private IList<IReservationService> services;

    public Reservation(IEnumerable<IReservationService> services)
    {
       this.services = new List<IReservationService>(services);
    }

    public IEnumerable Services<IReservationService> 
    { 
       get 
       {
          return this.services;
       }
    }
}

【讨论】:

    【解决方案3】:

    在查看接口范围时,您必须考虑两件事:

    1. 要求每个IReservation 实现这些成员是否有意义?
    2. 在没有成员 Y 的情况下引用成员 X 是否有意义?

    第一个是您所涵盖的内容,并且得出了“否”的结论。第二个同样重要。将某些东西视为“可以修改”而它不能做任何其他事情是否有意义?如果没有,请考虑制作IReservationIModifiableReservation,或其他一些功能分组。

    例如,CancelModify 似乎是相辅相成的,因此可能希望将它们放在一起在 IModifiableReservation 上,然后让您的类实现该接口。

    如你所见,这似乎有点过于细化了。

    【讨论】:

    • 感谢您提供简单而有效的答案。
    • 不需要创建fat IReservation,而是添加另一个细粒度的IBookableReservation。我已经把我的考虑放在下面的答案stackoverflow.com/a/16492280/52277
    猜你喜欢
    • 2011-10-28
    • 2014-06-20
    • 2015-07-24
    • 1970-01-01
    • 2016-12-05
    • 2011-04-04
    • 2016-04-24
    • 2011-03-07
    • 2011-02-06
    相关资源
    最近更新 更多