104

有人问我一个问题,我想在这里查看我的答案。

问:在哪种情况下扩展抽象类而不是实现接口更合适?

A:如果我们使用模板方法设计模式。

我对么 ?

如果我不能清楚地说明这个问题,我很抱歉。
我知道抽象类和接口之间的基本区别。

1)当要求是我们需要在每个子类中为特定操作(实现方法)实现相同功能而为某些其他操作(仅方法签名)实现不同功能时,使用抽象类

2)如果您需要将签名相同(并且实现不同),请使用接口,以便您可以遵守接口实现

3)我们可以扩展最多一个抽象类,但可以实现多个接口

重申问题:除了上面提到的之外,是否还有其他场景需要我们特别使用抽象类(一个是看到模板方法设计模式在概念上仅基于此)?

接口与抽象类

在这两者之间进行选择真的取决于你想做什么,但幸运的是,Erich Gamma 可以帮助我们一点。

与往常一样,需要权衡取舍,接口为您提供关于基类的自由,抽象类为您提供以后添加新方法的自由。——埃里希伽玛

不能去改变一个接口,而不必在你的代码中改变很多其他的东西,所以避免这种情况的唯一方法是创建一个全新的接口,这可能并不总是一件好事。

Abstract classes应该主要用于密切相关的对象。Interfaces更擅长为不相关的类提供通用功能。

4

15 回答 15

92

何时使用接口

接口允许某人从头开始实现您的接口或在其他一些代码中实现您的接口,这些代码的原始或主要目的与您的接口完全不同。对他们来说,您的界面只是附带的,必须添加到他们的代码中才能使用您的包。缺点是接口中的每个方法都必须是公共的。您可能不想公开所有内容。

何时使用抽象类

相比之下,抽象类提供了更多的结构。它通常定义一些默认实现,并提供一些对完整实现有用的工具。问题是,使用它的代码必须使用您的类作为基础。如果其他想要使用您的包的程序员已经独立开发了自己的类层次结构,那可能会非常不方便。在 Java 中,一个类只能从一个基类继承。

何时使用两者

您可以提供两全其美,一个接口和一个抽象类。如果他们愿意,实现者可以忽略您的抽象类。这样做的唯一缺点是通过接口名称调用方法比通过抽象类名称调用方法稍慢。

于 2012-04-06T06:47:11.323 回答
32

重申问题:除了上面提到的这些之外,还有任何其他场景需要我们使用抽象类(一个是模板方法设计模式在概念上仅基于此)

是的,如果您使用 JAXB。它不喜欢接口。您应该使用抽象类或使用泛型解决此限制。

来自个人博客文章

界面:

  1. 一个类可以实现多个接口
  2. 接口根本无法提供任何代码
  3. 接口只能定义公共静态最终常量
  4. 接口不能定义实例变量
  5. 添加新方法会对实现类产生连锁反应(设计维护)
  6. JAXB 无法处理接口
  7. 接口不能扩展或实现抽象类
  8. 所有接口方法都是公开的

一般来说,接口应该用来定义契约(要实现什么,而不是如何实现)。

抽象类:

  1. 一个类最多可以扩展一个抽象类
  2. 抽象类可以包含代码
  3. 抽象类可以定义静态和实例常量(最终)
  4. 抽象类可以定义实例变量
  5. 修改现有抽象类代码对扩展类有连锁反应(实现维护)
  6. 向抽象类添加新方法对扩展类没有连锁反应
  7. 抽象类可以实现接口
  8. 抽象类可以实现私有和受保护的方法

抽象类应该用于(部分)实现。它们可以成为限制 API 合同实施方式的一种手段。

于 2012-08-11T13:15:15.383 回答
18

当您遇到所有类具有相同结构但完全具有不同功能的情况时,将使用接口。

当您遇到所有类具有相同结构但某些相同和某些不同功能的场景时,将使用抽象类。

看看这篇文章:http ://shoaibmk.blogspot.com/2011/09/abstract-class-is-class-which-cannot-be.html

于 2012-04-06T06:47:03.273 回答
15

这里有很多很好的答案,但我经常发现同时使用接口和抽象类是最好的方法。 考虑这个人为的例子:

您是一家投资银行的软件开发人员,需要构建一个向市场下订单的系统。您的界面捕捉了交易系统功能的最一般概念,

1) Trading system places orders
2) Trading system receives acknowledgements

并且可以在界面中捕获,ITradeSystem

public interface ITradeSystem{

     public void placeOrder(IOrder order);
     public void ackOrder(IOrder order);

}

现在,在销售台和其他业务线工作的工程师可以开始与您的系统交互,为他们现有的应用程序添加下订单功能。你甚至还没有开始建造!这就是接口的力量。

因此,您继续为股票交易者构建系统;他们听说您的系统具有查找廉价股票的功能,并且非常渴望尝试一下!您在一个名为 的方法中捕获了这种行为findGoodDeals(),但同时也意识到与市场的连接涉及到很多混乱的东西。例如,您必须打开一个SocketChannel,

public class StockTradeSystem implements ITradeSystem{    

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

具体的实现会有很多这样的杂乱无章的方法connectToMarket(),但是findGoodDeals()交易者真正关心的是所有这些。

现在这是抽象类发挥作用的地方。 你的老板告诉你外汇交易员也想使用你的系统。看看货币市场,你会发现管道几乎与股票市场相同。事实上,connectToMarket()可以逐字重复使用来连接外汇市场。然而,findGoodDeals()在货币领域是一个截然不同的概念。因此,在将代码库传递给大洋彼岸的外汇天才小子之前,您首先要重构为一个abstract类,留下findGoodDeals()未实现的

public abstract class ABCTradeSystem implements ITradeSystem{    

    public abstract void findGoodDeals();

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

您的股票交易系统findGoodDeals()按照您已经定义的方式实施,

public class StockTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

但是现在外汇天才孩子可以通过简单地提供findGoodDeals()货币实现来构建她的系统;她不必重新实现套接字连接甚至接口方法!

public class CurrencyTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       ccys = <Genius stuff to find undervalued currencies>
       System.out.println("The best FX spot rates are: " + ccys);
    }

接口编程功能强大,但类似的应用程序通常以几乎相同的方式重新实现方法。使用抽象类避免了重新实现,同时保留了接口的功能。

注意:有人可能想知道为什么findGreatDeals()不是界面的一部分。请记住,接口定义了交易系统中最通用的组件。另一位工程师可能会开发一个完全不同的交易系统,他们并不关心找到好的交易。该界面保证了销售台也可以与他们的系统进行交互,因此最好不要将您的界面与“优惠”之类的应用程序概念纠缠在一起。

于 2016-12-20T15:57:30.593 回答
7

您应该使用哪个抽象类或接口?

如果这些语句中的任何一个适用于您的用例,请考虑使用抽象类:

您希望在几个密切相关的类之间共享代码。

您希望扩展抽象类的类具有许多公共方法或字段,或者需要公共以外的访问修饰符(例如受保护和私有)。

您要声明非静态或非最终字段。这使您能够定义可以访问和修改它们所属对象的状态的方法。

如果这些语句中的任何一个适用于您的用例,请考虑使用接口:

您希望不相关的类会实现您的接口。例如,接口 Comparable 和 Cloneable 由许多不相关的类实现。

您想指定特定数据类型的行为,但不关心谁实现了它的行为。

您想利用类型的多重继承。

http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html

于 2015-03-06T21:53:37.473 回答
4

在过去三年中,随着与 Java 8 版本交互的新功能的增加,情况发生了很大变化。

从界面上的oracle 文档页面:

接口是一种引用类型,类似于类,只能包含常量、方法签名、默认方法、静态方法和嵌套类型。方法体仅存在于默认方法和静态方法。

正如您在问题中引用的那样,抽象类最适合您必须创建骨架的模板方法模式。接口不能在这里使用。

更喜欢抽象类而不是接口的另一个考虑因素:

您在基类中没有实现,只有子类必须定义自己的实现。您需要抽象类而不是接口,因为您想与子类共享状态。

抽象类在相关类之间建立“是”关系,接口在不相关类之间提供“具有”能力


关于您问题的第二部分,这对大多数编程语言都有效,包括java-8发布之前的 java

与往常一样,需要权衡取舍,接口为您提供关于基类的自由,抽象类为您提供以后添加新方法的自由。——埃里希伽玛

你不能去改变一个界面,而不必在你的代码中改变很多其他的东西

如果你更喜欢抽象类而不是前面两个考虑的接口,你现在必须重新考虑,因为默认方法为接口添加了强大的功能。

默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

要在接口和抽象类之间选择其中之一,oracle 文档页面引用以下内容:

抽象类类似于接口。您不能实例化它们,它们可能包含声明的带有或不带有实现的方法的混合。但是,使用抽象类,您可以声明非静态和最终的字段,并定义公共、受保护和私有的具体方法。

使用接口,所有字段都自动是公共的、静态的和最终的,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,您只能扩展一个类,无论它是否是抽象的,而您可以实现任意数量的接口。

有关更多详细信息,请参阅这些相关问题:

接口 vs 抽象类(通用 OO)

我应该如何解释接口和抽象类之间的区别?

总结:现在天平更倾向于界面

除了上面提到的之外,是否还有其他场景需要使用抽象类(一个是模板方法设计模式在概念上仅基于此)?

除了模板方法模式之外,一些设计模式使用抽象类(通过接口)。

创作模式:

抽象工厂模式

结构模式:

装饰器模式

行为模式:

中介者模式

于 2016-03-18T18:46:04.447 回答
3

在我看来,基本的区别在于an interface can't contain non abstract methods while an abstract class can。所以如果子类共享一个共同的行为,这个行为可以在超类中实现,从而在子类中继承

我还引用了“Java 中的软件架构设计 ppatterns”一书中的以下内容

" 在 Java 编程语言中,不支持多重继承。这意味着一个类只能从一个类继承。因此,只有在绝对必要时才应使用继承。只要有可能,表示共同行为的方法应在Java 接口的形式由不同的实现者类实现。但是接口受到它们不能提供方法实现的限制。这意味着接口的每个实现者必须显式地实现接口中声明的所有方法,即使其中一些方法方法代表了功能中不变的部分,并且在所有实现类中都有完全相同的实现,这导致了冗余代码。以下示例演示了如何在这种情况下使用抽象父类模式,而无需冗余方法实现。”

于 2013-09-19T10:06:00.500 回答
3

你不正确。有很多场景。只是不可能将其简化为单个 8 字规则。

于 2012-04-06T06:43:25.243 回答
3

最短的答案是,当您寻求的某些功能已经在其中实现时,扩展抽象类。

如果实现接口,则必须实现所有方法。但是对于抽象类,您需要实现的方法数量可能会更少。

模板设计模式中,必须定义一个行为。此行为取决于其他抽象方法。通过创建子类并定义这些方法,您实际上定义了主要行为。底层行为不能在接口中,因为接口没有定义任何东西,它只是声明。所以模板设计模式总是带有一个抽象类。如果您想保持行为流完整,您必须扩展抽象类,但不要覆盖主要行为。

于 2012-04-06T06:49:21.697 回答
2

抽象类在两个重要方面不同于接口

  • 他们为所选方法提供默认实现(您的答案已涵盖)
  • 抽象类可以有状态(实例变量) - 所以这是你想用它们代替接口的另一种情况
于 2012-04-06T06:52:04.663 回答
1

这是我的理解,希望对你有帮助

抽象类:

  1. 可以有继承的成员变量(不能在接口中完成)
  2. 可以有构造函数(接口不能)
  3. 它的方法可以具有任何可见性(即:私有、受保护等 - 而所有接口方法都是公共的)
  4. 可以有定义的方法(带有实现的方法)

接口:

  1. 可以有变量,但都是public static final变量
    • 在静态范围内永远不会改变的常量值
    • 非静态变量需要一个实例,你不能实例化一个接口
  2. 所有方法都是抽象的(抽象方法中没有代码)
    • 所有代码都必须实际编写在实现特定接口的类中
于 2016-10-03T16:45:55.683 回答
1

抽象和接口的用法:

一个有“Is-A-Relationship”,另一个有“Has-A-Relationship”

默认属性已经设置在抽象中,额外的属性可以通过接口来表达。

示例:--> 在人类中,我们有一些默认属性,例如吃饭、睡觉等,但如果有人有任何其他课程活动,如游泳、玩耍等,则可以通过 Interface 表示。

于 2017-02-13T09:05:31.760 回答
1

这是一个很好的问题 这两个并不相似,但可以出于某些相同的原因使用,例如重写。创建时最好使用接口。归根结底,它有利于调试。

于 2016-09-19T06:17:35.627 回答
0

Abstract classes should be extended when you want to some common behavior to get extended. 抽象超类将具有共同行为,并将定义子类应实现的抽象方法/特定行为。

Interfaces allows you to change the implementation anytime allowing the interface to be intact.

于 2012-04-06T06:54:50.877 回答
0

我认为这里的答案缺少要点:

Java 接口(问题是关于Java,但在其他语言中也有类似的机制)是一种部分支持多重继承的方法,即仅方法继承。

它类似于 PHP 的特征或 Python 的鸭子类型

除此之外,没有什么是您真正需要的接口——而且您不能实例化 Java 接口。

于 2021-07-09T18:03:25.293 回答