有继承具体类的好例子吗?

背景:

作为一个 Java 程序员,我广泛地从接口继承(更确切地说: 实现) ,有时我设计抽象基类。然而,我从来没有真正感到需要子类化一个具体的(非抽象的)类(在我这样做的情况下,后来发现另一个解决方案,比如 代表团会更好)。

所以现在我开始觉得几乎没有从具体类继承是合适的情况。一方面,Liskov代换原则(LSP)似乎几乎不可能满足非平凡类; 另一方面,很多问题似乎也有类似的观点。

所以我的问题是:

在什么情况下(如果有的话)从一个具体的类继承是有意义的? 您能给出一个从另一个具体类继承的类的具体的、真实的例子吗? 在这个例子中,您认为这是给定约束的最佳设计?我对满足 LSP 的例子特别感兴趣(或者满足 LSP 似乎不重要的例子)。

我主要有 Java 背景,但是我对任何语言的例子都感兴趣。

12067 次浏览

这是我正在进行的一个当前实现的示例。

在 OAuth 2环境中,由于文档是在草稿阶段的 还是,所以规范一直在变化(在撰写本文时,我们的版本是21)。

因此,我必须扩展我的具体 AccessToken类来容纳不同的访问令牌。

在早期的草案中,没有设置 token_type字段,因此实际的访问令牌如下:

public class AccessToken extends OAuthToken {


/**
*
*/
private static final long serialVersionUID = -4419729971477912556L;
private String accessToken;
private String refreshToken;
private Map<String, String> additionalParameters;


//Getters and setters are here
}

现在,对于返回 token_type的 Access 令牌,我有

public class TokenTypedAccessToken extends AccessToken {


private String tokenType;
//Getter and setter are here...
}

因此,我可以同时返回这两个值,而最终用户对此一无所知。 : -)

总结: 如果您想要一个定制的类,它具有与您的具体类相同的功能而不改变具体类的结构,我建议扩展具体类。

对于接口 I,通常有一个 骨架实施方案。如果您可以提供不使用抽象方法(例如通过 hook)的扩展性,那么最好使用非抽象骨架类,因为您可以实例化它。

一个例子是 转发包装类,它能够转发到实现 I的具体类 C的另一个对象,例如启用 装饰或简单的代码重用 C,而不必从 C继承。您可以在 有效的爪哇第16项中找到这样的例子,优先于继承的组合。(出于版权的考虑,我不想在这里发布它,但它实际上只是将 I的所有方法调用转发到包装的实现)。

一般来说,我从具体类派生的唯一时间是它们在框架中的时候。从 AppletJApplet派生出来的是一个简单的例子。

  1. 如果希望扩展副库功能,只能选择继承具体类。

  2. 例如,您可以查看 DataInputStream 的层次结构,该层次结构实现了 FilterInputStream 的 DataInput 接口。

来自 Gdata 项目:

Service 被设计用作一个基类,可以针对特定类型的 GData 服务进行定制。

服务 javadoc:

Service 类表示到 GData 服务的客户端连接。它封装了与 GData 服务器的所有协议级交互,并作为高级实体(提要、条目等)的帮助类,这些实体调用服务器上的操作并处理它们的结果。

此类提供访问任何 GData 服务所需的基本级别公共功能。它还被设计用作一个基类,可以针对特定类型的 GData 服务进行自定义。支持定制的例子包括:

身份验证 -为需要身份验证并使用 HTTP 基本或摘要身份验证以外的其他内容的服务实现自定义身份验证机制。

扩展 -为提要、条目和与服务相关联的其他类型定义预期的扩展。

格式 -定义额外的自定义资源表示形式,这些表示形式可能由服务和客户端解析器和生成器使用或生成,以处理它们。

其他用例是覆盖默认行为:

假设有一个类使用标准的 Jaxb 解析器进行解析

public class Util{


public void mainOperaiton(){..}
protected MyDataStructure parse(){
//standard Jaxb code
}
}

现在假设我想对解析操作使用一些不同的绑定(比如 XMLBean) ,

public class MyUtil extends Util{


protected MyDataStructure parse(){
//XmlBean code code
}
}

现在我可以通过超类的代码重用来使用新的绑定。

我开始觉得,几乎没有任何情况下从具体类继承是合适的。

这是一个“几乎”。尝试在不扩展 AppletJApplet的情况下编写一个

这是一个来自 小应用程序信息页面的例子。

/* <!-- Defines the applet element used by the appletviewer. -->
<applet code='HelloWorld' width='200' height='100'></applet> */
import javax.swing.*;


/** An 'Hello World' Swing based applet.


To compile and launch:
prompt> javac HelloWorld.java
prompt> appletviewer HelloWorld.java  */
public class HelloWorld extends JApplet {


public void init() {
// Swing operations need to be performed on the EDT.
// The Runnable/invokeLater() ensures that happens.
Runnable r = new Runnable() {
public void run() {
// the crux of this simple applet
getContentPane().add( new JLabel("Hello World!") );
}
};
SwingUtilities.invokeLater(r);
}
}

我发现 java 集合类是一个很好的例子。 所以你有一个包含诸如 AbstractList、 AbstractSet、 AbstractQueue 等子元素的 AbstractCollection。 I think this hierarchy has been well designed.. and just to ensure there's no explosion there's the Collections class with all its inner static classes.

另一个很好的例子是数据存储类型。举一个精确的例子: 红黑树是一个更具体的二叉树,但检索数据和其他信息,如大小,可以处理相同。当然,一个好的库应该已经实现了这一点,但是有时您必须为您的问题添加特定的数据类型。

我目前正在开发一个应用程序,为用户计算矩阵。用户可以提供影响计算的设置。有几种类型的矩阵可以计算,但有明显的相似性,特别是在可配置性: 矩阵 A 可以使用矩阵 B 的所有设置,但有额外的参数可以使用。在这种情况下,我从 ConfigObjectB 继承了我的 ConfigObjectA,它工作得非常好。

一般来说,从抽象类继承比从具体类继承要好。一个具体的类必须为它的数据表示提供一个定义,并且一些子类将需要一个不同的表示。由于抽象类不必提供数据表示,未来的子类可以使用任何表示,而不必担心与它们继承的表示冲突。 即使我从来没有找到一个情况,我觉得具体的继承是必要的。但是在某些情况下可能需要具体的继承,特别是在为软件提供向下兼容的时候。在这种情况下,您可能已经专门化了一个 class A,但是您希望它是具体的,因为您的旧应用程序可能正在使用它。

我主要有 Java 背景,但是我对任何语言的例子都感兴趣。

像许多框架一样,ASP.NET 大量使用继承来在类之间共享行为。例如,输入密码有这样的继承层次结构:

System.Object
System.Web.UI.Control
System.Web.UI.HtmlControls.HtmlControl          // abstract
System.Web.UI.HtmlControls.HtmlInputControl   // abstract
System.Web.UI.HtmlControls.HtmlInputText
System.Web.UI.HtmlControls.HtmlInputPassword

其中可以看到派生自。

如果您正在构建一个框架——并且确定要这样做——那么您可能会发现自己需要一个很好的大型继承层次结构。

修饰模式是一种为类添加附加行为而又不使其过于通用的简便方法,它大量使用了具体类的继承。这里已经提到过了,但是在某种程度上用了一个学术名称“转发包装类”。

我认为下面是一个很好的例子,当它可以是适当的:

public class LinkedHashMap<K,V>
extends HashMap<K,V>

另一个很好的例子是异常的继承:

public class IllegalFormatPrecisionException extends IllegalFormatException
public class IllegalFormatException extends IllegalArgumentException
public class IllegalArgumentException extends RuntimeException
public class RuntimeException extends Exception
public class Exception extends Throwable

例如,可以在 GUI 库中这样做。继承一个简单的 Component 并将其委托给 Panel 没有多大意义。从专家小组直接继承遗产可能要容易得多。

我能想到的一个非常常见的情况是从基本的 UI 控件派生,例如窗体、文本框、组合框等。它们是完整的、具体的,并且能够很好地独立存在; 然而,它们中的大多数也是非常基本的,有时候它们的默认行为并不是你想要的。例如,几乎没有人会使用纯粹的 Form 实例,除非他们可能正在创建一个完全动态的 UI 层。

例如,在我最近编写的一个相对成熟的软件中(这意味着我没有时间专注于开发它了:) ,我发现我需要向 ComboBoxes 添加“延迟加载”功能,这样第一个窗口加载就不会花费50年(以计算机年数计算)。我还需要能够根据另一个可编辑控件中显示的内容自动过滤一个 ComboBox 中的可用选项,最后,我需要一种方法来“镜像”另一个可编辑控件中的一个 ComboBox 的值,并使一个控件的更改也发生在另一个控件中。因此,我扩展了基本的 ComboBox 来赋予它这些额外的特性,并创建了两个新类型: LazyComboBox,以及更进一步的 MirroringComboBox。两者都基于完全可用的、具体的 ComboBox 控件,只是覆盖了一些行为并添加了一些其他行为。它们不是非常松散耦合的,因此不是太牢固的,但是增加的功能是足够通用的,如果我不得不,我可以重写这些类从头开始做同样的工作,可能更好。

很多答案,但我想我会加上我自己的0.02美元。

我很少覆盖混合类,但是在某些特定情况下。当框架类被设计用于扩展时,至少已经提到了1个。我想到了另外两个例子:

1)如果我想调整一个具体类的行为。有时我想改变具体类的工作方式,或者我想知道什么时候调用某个方法,这样我就可以触发一些东西。通常,具体的类会定义一个钩子方法,它的唯一用途是让子类重写该方法。

示例: 我们覆盖了 MBeanExporter,因为我们需要能够取消注册 JMX bean:

public class MBeanRegistrationSupport {
// the concrete class has a hook defined
protected void onRegister(ObjectName objectName) {
}

我们班:

public class UnregisterableMBeanExporter extends MBeanExporter {
@Override
protected void onUnregister(ObjectName name) {
// always a good idea
super.onRegister(name);
objectMap.remove(name);
}

下面是另一个很好的例子。 LinkedHashMap被设计成覆盖它的 removeEldestEntry方法。

private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
return size() > 1000;
}

2)如果一个类与具体的类有大量的重叠,除了一些功能上的调整。

示例: 我的 奥姆利特项目处理持久化 Long对象字段和 long基元字段。两者的定义几乎完全相同。LongObjectType提供了描述数据库如何处理 long字段的所有方法。

public class LongObjectType {
// a whole bunch of methods

LongType覆盖了 LongObjectType,并且只调整了一个处理原语的方法。

public class LongType extends LongObjectType {
...
@Override
public boolean isPrimitive() {
return true;
}
}

希望这个能帮上忙。

由于您所说的原因,您的担忧在经典原则“ 喜欢合成胜过继承”中也得到了回应。我都不记得上次从具体类继承是什么时候了。任何需要被子类重用的公共代码几乎总是需要为这些类声明抽象接口。按照这个顺序,我倾向于采用以下策略:

  1. 组合(没有继承)
  2. 接口
  3. 抽象类

从具体类继承从来都不是一个好主意。

[编辑]我要对这个声明进行限定,我认为当 控制了架构时,就没有好的用例。当然,如果使用的是一个预期的 API,你会怎么做?但是我不理解这些 API 的设计选择。调用类应该始终能够根据 依赖反转原则声明和使用抽象。如果一个子类有额外的接口要使用,那么要么违反 DIP,要么进行一些丑陋的强制转换来获得这些接口。

只是一般的想法。抽象类漏掉了一些东西。如果每个派生类中所缺少的东西是不同的,那就说得通了。但是,您可能会遇到这样的情况: 您不想修改类,而只是想添加一些内容。为了避免代码的重复,您将继承。如果你同时需要这两个类,那就是从一个具体的类继承。

所以我的答案是: 在所有的情况下,你真的只想添加一些东西。也许这种事不常发生。