【问题标题】:Should one interface inherit another interface一个接口是否应该继承另一个接口
【发布时间】:2010-12-13 21:55:29
【问题描述】:

我似乎无法找到答案,只是想确保它是一个好的编码标准。我有许多不同的类使用的接口A,并且不希望接口A 改变。我遇到了一个新要求,许多实现接口A 的类都需要一个枚举,但并非所有类都需要这个枚举。我不希望不需要这个新枚举的类来实现这个新功能。所以我创建了接口B,其中包含我需要添加的新枚举。然后我让接口B继承接口A,这是我关心的问题,一个接口可以继承另一个接口吗?为了继续我的更改,我更改了需要新枚举来实现接口B 而不是接口A 的类,因为它是由接口B 继承的。我曾考虑在需要它们的类中实现这两个接口,但我在整个代码中都使用该接口,并且希望只使用一个接口来查看类而不是两个。

我希望这已经足够清楚(可能太长了),但如果有人可以就这方面给我一些建议,要么我做对了,要么我做错了,请告诉我。

谢谢!

【问题讨论】:

    标签: .net oop inheritance


    【解决方案1】:

    接口继承是一个很好的工具,但你应该只在接口 B 真正可以替代接口 A 时使用它,而不仅仅是聚合松散相关的行为。

    很难判断它是否适合您的具体情况,但原则上使用这种做法并没有错。您一直在一流的 API 中看到它。仅从 .NET 框架中选择一个常见示例:

    public interface ICollection<T> : IEnumerable<T>, IEnumerable
    

    【讨论】:

    • 我最初建议应该应用 Liskov 替换原则。回想起来,这并不重要。 LSP 的大多数要求(保持不变量、对前置条件和后置条件的限制)实际上只适用于具体实现,而不适用于接口。话虽如此,可替换性的一般原则仍应指导接口继承决策。
    • Liskov 替换原则:确保接口 B 可以完全替换接口 A 很重要。否则,您最终会得到您不想要的功能。这会导致您不需要额外的代码,从而降低软件的稳定性。
    • 我发现它在用外观替换现在需要第二个实现的现有依赖项时效果很好。外观没有更改原始类和所有测试以需要工厂,而是使用工厂处理并返回所需的结果。我使用标记接口将两个不同的接口注入到外观将使用的工厂中。只有工厂知道“标记”接口。不理想,但我更喜欢它而不是更改现有的类。
    【解决方案2】:

    IMO 这正是正确的方法,我认为它没有任何问题。

    【讨论】:

      【解决方案3】:

      当然可以有一个接口的继承树,甚至是接口的“多重继承”。这是否是正确的做法取决于所讨论的接口。如果确实是接口 B 是接口 A 的扩展或改进,那么继承是有道理的,但如果新的枚举与接口 A 表达的概念在很大程度上无关,我会让它们成为两个独立的接口并拥有类需要实现这两个接口。

      【讨论】:

        【解决方案4】:

        从技术上讲,接口不会相互继承。当你创建一个继承自IBarIFoo 时,真正发生了什么,你是说任何实现IFoo 的类也必须实现IBar

        interface IBar
        {
            void DoBar();
        }
        
        interface IFoo : IBar
        {
            void DoFoo();
        }
        

        在此示例中,IFoo 接口没有 DoBar() 方法。大多数情况下,这种区别并不重要,但是当在接口而不是类上使用反射时,它会咬你一口。

        【讨论】:

        • 即使考虑到您所描述的行为(在 Phil Haack 最近的专栏 haacked.com/archive/2009/11/10/… 中进行了非常详细的描述),我认为在C# 当 C# 参考文档甚至使用该术语时,如“一个接口可以继承自一个或多个基本接口”。 (msdn.microsoft.com/en-us/library/87d83y5b%28VS.80%29.aspx) 我只想说,在 C# 中,接口继承在反映继承成员时的行为与类继承不同。
        • 很公平。我想这取决于如何定义“继承”。当然,无论是类还是接口,C# 语法都是相同的。但是,如果您认为继承包括父项的成员,那么当您谈论接口时,您的心智模型与现实不符。
        • 感谢您指出这一点。没有意识到这是处理接口反射时的工作原理。
        【解决方案5】:

        我认为数据库总是提供一种很好的方式来演示接口,因此考虑一个接口是否应该继承另一个接口,请看以下内容,

        IMySqlDatabase : IDatabase
        MySqlDatabase : IMySqlDatabase
        
        IMsSqlDatabase : IDatabase
        MsSqlDatabase : IMsSqlDatabase
        

        MySqlDatabase 是 IMySqlDatabase,IMySqlDatabase 是 IDatabase。

        现在,如果您需要更改您的 IDatabase 接口,它的孙子(具体数据库类)可以获得好处,但您不必扩展 MySQL 和 MsSQL(或者甚至可能更多 DBMS 的接口)。同时,在您的中间人 (IMsSqlDatabase) 中,您仍然可以拥有 MySQL 或 Oracle DB 不支持的接口功能。

        【讨论】:

          【解决方案6】:

          考虑接口是否应该在逻辑上配对,如果你觉得它们可以很好地相互配合,那么绝对使用继承。

          我们来看一个例子;

          public interface IScanner
          {
              void Scan();
          }
          
          public interface IPrinter
          {
              void Print();
          }
          

          打印机和扫描仪通常是独立的对象,每个都有自己的功能,但是这两个设备通常在同一设备中配对;

          public interface IPhotocopier : IScanner, IPrinter
          {
              void Copy();
          }
          

          IPhotocopier 应该从 IScanner 和 IPrinter 继承是有道理的,因为这现在允许复印机除了作为复印机的主卷筒外,还可以用作扫描仪或打印机(它包含)。

          现在让我们再看一个界面;

          public interface IBlender
          {
              void Blend();
          }
          

          让 IBlender 被任何早期接口继承是没有意义的(你会怎么称呼它们?IBlendingScanner?)。

          如果您不能给新接口起一个合理的名称,这可能表明您可能不想在这种情况下使用继承。

          继承一些接口(如 IDisposable)是个坏主意,因为这会强制新接口的所有实现都实现 dispose 模式,即使它们没有任何可支配资源。

          【讨论】:

            猜你喜欢
            • 2017-02-07
            • 1970-01-01
            • 2012-05-12
            • 2018-07-07
            • 1970-01-01
            • 2012-08-24
            • 2010-10-22
            • 2020-12-09
            • 2013-10-22
            相关资源
            最近更新 更多