关于Java中抽象类与接口的设计模式

Abstract class vs Interface in Java

有人问我一个问题,我想让我的答案在这里复习一下。

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

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

我说的对吗?

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

1)当需要在每个子类中为特定操作实现相同的功能(实现方法)以及为其他一些操作实现不同的功能(仅方法签名)时,使用抽象类。

2)如果需要将签名放在相同的位置(并且实现不同),则使用interface,这样您就可以遵守接口实现。

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

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

接口与抽象类

这两者之间的选择真的取决于你想做什么,但幸运的是,埃里希伽马可以帮助我们一点。

正如通常存在的权衡一样,接口给了您关于基类的自由,抽象类给了您稍后添加新方法的自由。- Erich Gamma

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

Abstract classes主要用于关系密切的对象。Interfaces在为无关类提供公共功能方面更为出色。


何时使用接口

接口允许有人从头开始实现您的接口,或者用其他一些代码实现您的接口,这些代码的原始或主要目的与您的接口有很大的不同。对他们来说,您的接口只是偶然的,一些必须添加到他们的代码上才能使用您的包的东西。缺点是接口中的每个方法都必须是公共的。你可能不想暴露一切。

何时使用抽象类

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

何时同时使用

您可以提供两个世界中最好的,一个接口和一个抽象类。实现者可以忽略抽象类(如果他们选择的话)。这样做的唯一缺点是通过接口名调用方法比通过抽象类名调用方法稍慢。


reiterating the question: there is any other scenario besides these
mentioned above where specifically we require to use abstract class
(one is see is template method design pattern is conceptually based on
this only)

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

从个人博客帖子:

接口:

  • 类可以实现多个接口
  • 接口根本无法提供任何代码
  • 接口只能定义公共静态final常量
  • 接口无法定义实例变量
  • 添加新方法会对实现类(设计维护)产生涟漪效应。
  • JAXB无法处理接口
  • 接口不能扩展或实现抽象类
  • 所有接口方法都是公共的
  • 一般来说,应使用接口来定义合同(要实现什么,而不是如何实现)。

    抽象类:

  • 类最多可以扩展一个抽象类
  • 抽象类可以包含代码
  • 抽象类可以定义静态常量和实例常量(final)
  • 抽象类可以定义实例变量
  • 对现有抽象类代码的修改会对扩展类(实现维护)产生连锁反应。
  • 向抽象类中添加新方法对扩展类没有涟漪效应
  • 抽象类可以实现接口
  • 抽象类可以实现私有和受保护的方法
  • 抽象类应该用于(部分)实现。它们可能是限制API合同实施方式的一种手段。


    当您的场景中所有类都具有相同的结构,但完全具有不同的功能时,将使用接口。

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

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


    在过去的三年里,随着Java 8发布的新功能的加入,情况发生了很大变化。

    从界面上的Oracle文档页面:

    An interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods.

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

    另一个需要考虑的问题是,与接口相比,抽象类更受欢迎:

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

    抽象类建立了相关类之间的"是"关系,接口提供了不相关类之间的"有"能力。

    关于您的问题的第二部分,它对于Java8发布之前的大多数编程语言(包括Java)都是有效的。

    As always there is a trade-off, an interface gives you freedom with regard to the base class, an abstract class gives you the freedom to add new methods later. – Erich Gamma

    You can’t go and change an Interface without having to change a lot of other things in your code

    如果您喜欢抽象类与前面提到的两个接口,那么现在必须重新考虑,因为默认方法已经为接口添加了强大的功能。

    Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

    要在接口和抽象类之间选择其中一个,Oracle文档页引用了:

    Abstract classes are similar to interfaces. You cannot instantiate them, and they may contain a mix of methods declared with or without an implementation. However, with abstract classes, you can declare fields that are not static and final, and define public, protected, and private concrete methods.

    With interfaces, all fields are automatically public, static, and final, and all methods that you declare or define (as default methods) are public. In addition, you can extend only one class, whether or not it is abstract, whereas you can implement any number of interfaces.

    请参阅这些相关问题以了解更多详细信息:

    接口与抽象类(常规OO)

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

    总而言之:天平现在向界面倾斜得更多了。

    Are there any other scenarios, besides those mentioned above, where specifically we require to use abstract class (one is see is template method design pattern is conceptually based on this only)?

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

    创造模式:

    抽象工厂模式

    结构型式:

    装饰图案

    行为模式:

    调解人模式


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

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

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

    您希望扩展抽象类的类具有许多公共方法或字段,或者需要除public之外的访问修饰符(如protected和private)。

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

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

    您期望不相关的类实现您的接口。例如,可比较和可克隆的接口由许多不相关的类实现。

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

    您希望利用类型的多重继承。

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


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

    我还引用了下面的"Java中的软件体系结构设计pPotots"一书

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


    最简单的答案是,当抽象类中已经实现了Uou Seek的一些功能时,扩展抽象类。

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

    在模板设计模式中,必须定义行为。这种行为依赖于其他抽象的方法。通过创建子类并定义这些方法,您实际上定义了主要行为。基础行为不能在接口中,因为接口没有定义任何东西,它只是声明。所以模板设计模式总是伴随着一个抽象类。如果要保持行为流的完整性,则必须扩展抽象类,但不要重写主要行为。


    你不正确。有很多种情况。只是不可能把它简化成一个8字的规则。


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

    你是一家投资银行的软件开发人员,需要建立一个向市场投放订单的系统。你的界面捕捉到了交易系统所做的最一般的想法,

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

    可以在接口中捕获,ITradeSystem

    1
    2
    3
    4
    5
    6
    public interface ITradeSystem{

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

    }

    现在,在销售台和其他业务部门工作的工程师可以开始与您的系统交互,将订单放置功能添加到他们现有的应用程序中。你还没开始建造呢!这就是接口的力量。

    所以你继续为股票交易者建立系统;他们听说你的系统有一个特性,可以找到便宜的股票,并且非常渴望尝试它!你可以用一种称为findGoodDeals()的方法来捕捉这种行为,但同时也要意识到,在连接到市场的过程中,有很多混乱的东西。例如,您必须打开一个SocketChannel

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 =
           System.out.println("The best stocks to buy are:" + deals);
        }

    具体的实现会有很多这样混乱的方法,比如connectToMarket(),但是findGoodDeals()是交易者真正关心的。

    下面是抽象类发挥作用的地方。你的老板告诉你,外汇交易员也希望使用你的系统。看看货币市场,你会发现管道和股票市场几乎是一样的。事实上,connectToMarket()可以被逐字重复使用,以连接到外汇市场。然而,在货币领域,findGoodDeals()是一个截然不同的概念。因此,在将代码库传递给大洋彼岸的外汇Wiz孩子之前,您首先要重构成一个abstract类,这样findGoodDeals()就不会受到影响。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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()

    1
    2
    3
    4
    5
    6
    public class StockTradeSystem extends ABCTradeSystem{    

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

    但是现在,fx-whiz-kid可以通过简单地为货币提供findGoodDeals()的实现来构建她的系统;她不需要重新实现套接字连接甚至接口方法!

    1
    2
    3
    4
    5
    6
    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()不是接口的一部分。记住,该接口定义了交易系统最通用的组件。另一位工程师可能会开发一个完全不同的交易系统,在那里他们不关心如何找到好的交易。该接口保证了销售台也可以与他们的系统进行接口,因此最好不要将您的接口与"大交易"之类的应用程序概念纠缠在一起。


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

    • 它们提供所选方法的默认实现(由您的答案覆盖)
    • 抽象类可以有状态(实例变量)-所以这是您希望用它们代替接口的另一种情况


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


    抽象和接口的用法:

    一个有"IS-A-relationship",另一个有"HAS-A-relationship"

    默认属性是抽象设置的,额外的属性可以通过接口来表示。

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


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

    抽象类:

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

  • 可以有变量,但它们都是公共静态最终变量
    • 不随静态范围变化的常量值
    • 非静态变量需要实例,不能实例化接口
  • 所有方法都是抽象的(抽象方法中没有代码)
    • 所有代码必须实际写入实现特定接口的类中。

  • 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