代码之家  ›  专栏  ›  技术社区  ›  Blixt

何时使用接口或抽象类?何时同时使用两者?

  •  33
  • Blixt  · 技术社区  · 15 年前

    虽然某些指导原则声明,当您想要为继承不明确的类定义协定时,应该使用接口( IDomesticated )当类是另一个类的扩展时继承( Cat : Mammal , Snake : Reptile ,有些情况下(我认为)这些指导方针进入灰色区域。

    例如,假设我的实现是 Cat : Pet . Pet 是抽象类。应该扩大到 Cat : Mammal, IDomesticated 哪里 Mammal 是抽象类,并且 异构的 是接口吗?或者我与 KISS / YAGNI 原则(即使我不确定是否会有 Wolf 将来无法继承的类 宠物 )?

    远离隐喻 Cat S和 宠物 S,假设我有一些类表示传入数据的源。他们都需要以某种方式实现相同的基础。我可以在抽象中实现一些通用代码 Source 类并从中继承。我也可以做一个 ISource 界面(我觉得更“正确”)并在每个类中重新实现通用代码(这不太直观)。最后,我可以通过创建抽象类和接口来“吃蛋糕”。什么是最好的?

    这两种情况提出了仅使用抽象类、仅使用接口以及同时使用抽象类和接口的要点。这些都是有效的选择,还是有“规则”来规定何时应该使用一个而不是另一个?


    我想澄清一下,通过“同时使用抽象类和接口”,当它们本质上代表相同的东西时,包括这种情况( 来源 伊索尔 这两个类都有相同的成员),但类在接口指定协定时添加了通用功能。

    值得注意的是,这个问题主要是针对不支持多重继承的语言(例如.NET和Java)。

    7 回复  |  直到 8 年前
        1
  •  35
  •   Mark Seemann    8 年前

    作为第一条经验法则,我更喜欢抽象类而不是接口, based on the .NET Design Guidelines . 这种推理比.NET应用的范围广得多,但在书中有更好的解释。 Framework Design Guidelines .

    抽象基类首选项背后的主要原因是版本控制,因为您总是可以在不破坏现有客户机的情况下向抽象基类添加新的虚拟成员。使用接口是不可能的。

    在某些情况下,接口仍然是正确的选择(尤其是在您不关心版本控制的情况下),但了解其优点和缺点可以使您做出正确的决定。

    因此,作为我继续之前的一个部分答案:只有当您首先决定对接口进行编码时,同时拥有接口和基类才有意义。如果您允许一个接口,那么您必须只针对该接口进行编码,否则您将违反Liskov替换原则。换句话说,即使提供实现接口的基类,也不能让代码使用该基类。

    如果您决定对一个基类进行编码,那么拥有一个接口是没有意义的。

    如果决定对接口进行编码,则具有提供默认功能的基类是可选的。这是不必要的,但可能会加速实现者的工作,所以您可以提供一个作为礼貌。

    在ASP.NET MVC中有一个很好的例子。请求管道在IController上工作,但有一个控制器基类通常用于实现行为。

    最后一个答案:如果使用抽象基类,请仅使用它。如果使用接口,则基类是对实现者的可选礼节。


    更新: 不再 比起接口,我更喜欢抽象类,而且我很久没有这样做了;相反,我更喜欢组合而不是继承,使用solid作为指导原则。

    (虽然我可以直接编辑上面的文本,但这将彻底改变文章的性质,而且由于一些人发现它有足够的价值去投票,我宁愿让原文站着,而不是添加这个注释。文章的后一部分仍然有意义,因此删除它也是一个遗憾。)

        2
  •  20
  •   kyoryu    15 年前

    我倾向于使用基类(抽象的或非抽象的)来描述什么东西 ,而我使用接口来描述 能力 物体的

    是一个 哺乳动物,但其中之一 能力 是不是很可爱?

    或者,换句话说,类是名词,而接口更接近形容词。

        3
  •  12
  •   Gilles 'SO- stop being evil'    12 年前

    来自MSDN, Recommendations for Abstract Classes vs. Interfaces

    • 如果预期要创建组件的多个版本,请创建一个抽象类。抽象类提供了一种简单易行的方法来版本化您的组件。通过更新基类,所有继承类都将随更改自动更新。另一方面,接口一旦创建就不能更改。如果需要接口的新版本,则必须创建一个全新的接口。

    • 如果您正在创建的功能在各种不同对象中都很有用,请使用接口。抽象类应该主要用于密切相关的对象,而接口最适合为不相关的类提供公共功能。

    • 如果要设计小而简洁的功能,请使用接口。如果要设计大型功能单元,请使用抽象类。

    • 如果要在组件的所有实现中提供通用的、实现的功能,请使用抽象类。抽象类允许您部分实现类,而接口不包含任何成员的实现。

        4
  •  2
  •   starblue    15 年前

    如果要提供完全替换实现的选项,请使用接口。这尤其适用于主要组件之间的交互,这些组件应该总是通过接口分离。

    还有一些技术原因可以选择接口,例如在单元测试中启用模拟。

    在组件内部,可以直接使用抽象类访问类的层次结构。

    如果您使用一个接口并具有实现类的层次结构,那么拥有一个包含实现公共部分的抽象类是很好的做法。例如。

    interface Foo
    abstract class FooBase implements Foo
    class FunnyFoo extends FooBase
    class SeriousFoo extends FooBase
    

    对于更复杂的层次结构,还可以有更多的抽象类相互继承。

        5
  •  2
  •   Peter Mortensen stimpy    8 年前

    我总是使用这些准则:

    • 使用多类型继承的接口(如.NET/Java不使用多重继承)
    • 为类型的可重用实现使用抽象类

    主要关注点规则规定一个类总是有一个主要关注点和0个或更多其他关注点(参见 http://citeseer.ist.psu.edu/tarr99degrees.html )然后通过接口实现的那些0或更多的其他类型,因为类随后实现了它必须实现的所有类型(它自己的以及它实现的所有接口)。

    在多个实现继承的世界中(例如C++/Eiffel),人们将从实现接口的类继承。(理论上)。在实践中,它可能不会很好地工作。)

        6
  •  2
  •   Peter Mortensen stimpy    8 年前

    还有一种叫做 DRY 原则-不要重复你自己。

    在您的数据源示例中,您说在不同的实现之间有一些通用代码。在我看来,最好的处理方法是使用一个包含泛型代码的抽象类和一些扩展它的具体类。

    优点是,通用代码中的每个错误修复都有利于所有具体实现。

    如果只使用界面,则必须维护同一代码的多个副本,这会带来麻烦。

    关于抽象+接口,如果没有直接的理由,我不会这样做。从抽象类中提取接口是一种简单的重构,所以我只在实际需要时才这样做。

        7
  •  0
  •   Community c0D3l0g1c    7 年前

    关于一般准则,请参考以下SE问题:

    Interface vs Abstract Class (general OO)

    接口的实际用例:

    1. 执行 Strategy_pattern :将策略定义为接口。在运行时使用策略的一个具体实现动态切换实现。

    2. 定义一个 能力 在多个不相关的类中。

    抽象类的实际用例:

    1. 执行 Template_method_pattern :定义算法的框架。子类不能改变Algortihm的结构,但它们可以重新定义子类中实现的一部分。

    2. 如果要在多个相关类之间共享非静态和非最终变量,请使用“ 有一个 “关系”。

    Abstradt类和接口的使用:

    如果要使用抽象类,可以将抽象方法移动到接口,而抽象类可以简单地实现该接口。抽象类的所有用例都可以属于这个类别。