从具体类继承的任何好例子?

时间:2011-09-21 07:22:07

标签: java oop inheritance liskov-substitution-principle

背景:

作为Java程序员,我从接口广泛继承(而不是:实现),有时我设计抽象基类。但是,我从来没有真正觉得需要子类化一个具体的(非抽象)类(在我这样做的情况下,后来发现另一个解决方案,例如delegation会更好)。 / p>

所以现在我开始觉得几乎没有从具体类继承的合适的情况。首先,Liskov substitution principle(LSP)似乎几乎不可能满足非平凡类;另外many其他questions似乎也回应了类似的观点。

所以我的问题:

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

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

18 个答案:

答案 0 :(得分:20)

您经常为接口I提供骨架实施。如果你可以在没有抽象方法的情况下提供可扩展性(例如通过钩子),那么最好有一个非抽象的骨架类,因为你可以实例化它。

一个例子是转发包装类,以便能够转发到实现C的具体类I的另一个对象,例如启用decorationC的简单代码重用,而无需继承C。您可以在Effective Java项目16中找到这样的示例,支持组合而不是继承。 (我不想在这里发布它是因为版权,但它实际上只是简单地将I的所有方法调用转发给包装的实现。)

答案 1 :(得分:18)

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

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

答案 2 :(得分:14)

我能想到的一个非常常见的情况是从基本的UI控件中获取,例如表单,文本框,组合框等。它们是完整的,具体的,并且能够独立存在;但是,它们中的大多数也非常基本,有时它们的默认行为不是你想要的。例如,几乎没有人会使用未掺杂表单的实例,除非他们可能正在创建一个完全动态的UI层。

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

答案 3 :(得分:12)

一般来说,我从具体课程中获得的唯一时间是他们在框架中。从AppletJApplet派生是一个简单的例子。

答案 4 :(得分:7)

这是我正在进行的当前实施的一个例子。

在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...
}

所以,我可以返回两者,最终用户也不是更聪明。 : - )

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

答案 5 :(得分:6)

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

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

public class Util{

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

现在说我想使用一些不同的绑定(Say XMLBean)进行解析操作,

public class MyUtil extends Util{

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

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

答案 6 :(得分:6)

  

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

与许多框架一样,ASP.NET大量使用继承来共享类之间的行为。例如,HtmlInputPassword具有此继承层次结构:

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

其中可以看到从中派生的具体类的例子。

如果您正在构建一个框架 - 并且您确定要这样做 - 您可能会发现自己想要一个漂亮的大继承层次结构。

答案 7 :(得分:4)

decorator pattern是一种向类添加额外行为而不会使其过于通用的便捷方法,它大量使用具体类的继承。这里已经提到了,但在某种程度上是一个科学名称“转发包装类”。

答案 8 :(得分:3)

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

我很少重写concreate类,但在某些特定情况下。当框架类被设计为扩展时,至少已经提到过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)如果一个类与具体类共享大量重叠,除了对功能进行一些调整。

示例:我的ORMLite项目处理持久的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;
    }
}

希望这有帮助。

答案 9 :(得分:2)

通常,从抽象类继承而不是从具体类继承更好。具体类必须为其数据表示提供定义,并且某些子类将需要不同的表示。由于抽象类不必提供数据表示,因此未来的子类可以使用任何表示,而不必担心与它们继承的那些冲突。 即使我从未发现过某种情况,我觉得必须具备具体的遗产。但是,当您向软件提供向后兼容性时,可能会出现一些具体的继承性情况。在这种情况下,你可能有专门的class A,但你希望它具体,因为你的旧应用程序可能正在使用它。

答案 10 :(得分:2)

由于您所述的原因,您的疑虑也会在经典原则“favor composition over inheritance”中得到回应。我记不起上次从一个具体的班级继承了。需要由子类重用的任何公共代码几乎总是需要为这些类声明抽象接口。按此顺序,我尝试采用以下策略:

  1. 组成(没有继承)
  2. 接口
  3. 抽象类
  4. 从具体类继承真的不是一个好主意。

    [编辑]我将对此声明进行限定,并说当控制架构时,我没有看到它的好用例。当然,当使用期望它的API时,whaddaya会怎么做?但我不理解这些API所做的设计选择。调用类应始终能够根据Dependency Inversion Principle声明和使用抽象。如果子类具有要使用的其他接口,则您必须违反DIP或进行一些丑陋的转换以获取这些接口。

答案 11 :(得分:2)

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

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

答案 12 :(得分:2)

  

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

这是一个'差不多'。尝试撰写而不展开AppletJApplet

这是例如来自applet info. page

/* <!-- 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);
    }
}

答案 13 :(得分:2)

  1. 如果要扩展侧库功能,继承具体类是唯一的选择。

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

答案 14 :(得分:1)

来自gdata project

com.google.gdata.client.Service旨在充当可针对特定类型的GData服务进行自定义的基类。

服务javadoc:

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

此类提供访问任何GData服务所需的基本级通用功能。它还可以作为基类,可以针对特定类型的GData服务进行自定义。 支持的自定义项示例包括:

身份验证 - 为需要身份验证的服务实现自定义身份验证机制,并使用HTTP基本或摘要式身份验证之外的其他功能。

扩展 - 定义Feed,条目和与服务相关的其他类型的预期扩展。

格式 - 定义可能由服务和客户端解析器和生成器使用或生成的其他自定义资源表示来处理它们。

答案 15 :(得分:0)

我发现java集合类是一个非常好的例子。 所以你有一个AbstractCollection与子类,如AbstractList,AbstractSet,AbstractQueue ... 我认为这种层次结构设计得很好..只是为了确保没有爆炸,Collections类及其所有内部静态类。

答案 16 :(得分:0)

例如,您可以在GUI库中执行此操作。从单纯的Component继承并委托给Panel是没有多大意义的。直接从小组继承可能更容易。

答案 17 :(得分:0)

只是一个普遍的想法。抽象类缺少一些东西。如果缺少的东西在每个派生类中都是不同的,那么这是有意义的。但是你可能有一个案例,你不想修改一个类,只想添加一些东西。为了避免重复代码,您将继承。如果你需要这两个类,它将是一个具体类的继承。

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

相关问题