`
frank-liu
  • 浏览: 1681098 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

composition over inheritance及实例分析

 
阅读更多

前言

    相信学过一些OO的人都对几个基本的面向对象设计原则印象深刻,比如面向抽象、liskov、开闭原则等。这里重点讨论一下composition over inheritance的思想和jdk本身设计中的几个反例。希望通过这些分析能够有一个更深刻的理解而不至于浮于表面。

概念及对比

    关于composition以及inheritance的概念其实再简单不过了。我们在创建的某个类中包含对其他类的引用,在实现某些功能的时候实际上是利用到了被引用类的一些方法或者功能。从相互关系来看,这相当于是一个很简单的直接引用。这就是composition。

    而inheritance在我们刚开始学习某种OO的语言时都会提到。一个类如果继承了另外一个类,则它可以重用父类里面的方法和属性。从使用的角度来说,这个子类必须是这个父类的某一个特殊的类型,他们是一种is a的关系。比如说我们定义animal这么一个类,那么如果我们要定义猫或者狗的类是,可以继承这个animal,因为猫和狗都是一种animal。

    既然我们已经理解了这些基本的概念之后,在哪些情况下使用composition合适而在什么时候使用inheritance合适呢?虽然从原来OO的设计原则来看是应该优先考虑composition,那么是否就应该盲目的一律用composition呢?

    对于这两种情况的选择,我们可以在针对问题场景的时候先如此考虑。我们新的类和原有类是否为is a的关系呢?如果是的话,则意味着应用于父类的所有方法以及属性必然适用于子类。比如说我们有一个类Person:

public class Person {
    private String name;
    private String id;

    public String getName() {
        return name;
    }

    public void setName(String name) {
       this.name = name;
    }
    public void printName() {
        System.out.println(name);
    }
}

     如果我们要定义一个新的Employee的类,它本身也有name, id。而且也有显示name信息的方法要求。这个时候,我们发现如果继承Person的话,父类里的属性可以被Employee所拥有, 关键是父类里所有的方法对于子类来说都是适用的。那么这个Employee的类可以定义成如下:

public class Employee extends Person {
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}

    这里我们看到最重要的一点是Employee继承了Person了之后,可以在它本身调用父类的getName, setName以及printName这些方面。它本身还增加了title相关的方法。这里实现了一个理想的重用。

    现在,我们再换个思路来看他们之间的关系,如果我们用composition的方式来实现Employee,则该如何呢?首先我们要看到,Employee里面也需要name, id。那么他们相关的方法在Employee里面也需要定义。那么具体的实现代码则应该如下:

public class Employee {
    private Person person;

    private String title;

    public Employee(Person person) {
        this.person = person;
    }

    public void setName(String name) {
        this.person.setName(name);
    }

    public String getname() {
        return person.getName();
    }

    public void printName() {
        person.printName();
    }

    public void setTitle(String title) {
        this.title = title;
    }
    // ... omitted
}

    这里的代码也实现了同样的功能,和前面的比较起来需要一个额外的以Person为参数的构造函数。而且,最重要的就是需要额外写一遍和Person里定义的同样的方法,但是因为可以直接用Person里的方法,就直接调用Person里的。这样就额外的写了一些delegate的代码。所以,我们可以看到,虽然composition也可以实现inheritance的这些功能,在满足inheritance的条件下,有一大堆的delegate的代码要写。这样就平添了很多无意义的代码。

    所以我们在比较这两种情形的取舍时,可以先看看新的类是否可以完全适用于父类的所有场景,如果可以使用inheritance,否则应该考虑composition。这里举的示例是一个简单的情形。下面我们再看一些实际项目中使用inheritance不当的地方。

实例

    这里的一些示例取自jdk里面的代码。有些由于版本和当初设计的考虑,忽略了这么一些问题,因此会存在一些使用的隐患,而且影响到后续的使用以及修正。

Properties vs HashTable

    java.util.Properties,这个类在java里面算是比较常用的。主要用来保存我们读取的配置文件信息。在很多应用里,我们需要设置一些环境相关的值,针对不同的执行环境以及要求进行修改。这个时候我们一般会设置一个xxx.properties的文件。文件名可以随意设定,然后在读取文件和解析的程序里将文件名传入。(关于properties文件的读取可以参考这篇文章)。关键是,我们读取到的内容就保存在一个Properties类型的对象里。我们可以通过getProperty(String key)来读取特定的配置属性值。我们这里应用的时候,有一个很重要的设定就是,我们所要读写访问的属性都是String类型的。

    这里怎么会扯上HashTable呢?(HashTable详细实现分析可以参考这篇文章)因为我们Properties里面访问的本质上就是一组组的名值对,而且在jdk里Properties的实现就是通过继承HashTable做的。

如下是部分Properties的代码:

public class Properties extends Hashtable<Object,Object> {

     private static final long serialVersionUID = 4112578634029874840L;

     protected Properties defaults;

    public Properties() {
        this(null);
    }

    public Properties(Properties defaults) {
        this.defaults = defaults;
    }
  // ...omitted

}

    这里Properties的设计为什么不太合理呢?从前面我们的讨论来看,我们所有应用于HashTable的场景都适用于Properties吗?不一定吧?首先一个,HashTable里可以支持的参数不仅限于String类型,而Properties只能使用String类型。如果我们定义一个Properties的对象,然后我们就可以使用HashTable里的get, put等方法。而且往里面传入一些非String类型的参数。反正Properties继承的是HashTable<object, object>。只要是对象就行。这些方法对于Properties来说其实是没有意义的。如果我们故意使坏,利用put方法往里面放一些非String类型的对象,然后再用Properties的getProperties方法去取。在Properties的getProperty方法里是使用了父类HashTable的get方法,其具体实现如下:

public String getProperty(String key) {
        Object oval = super.get(key);
        String sval = (oval instanceof String) ? (String)oval : null;
        return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
    }

    在后面的读取中虽然不会取到我们乱加入的对象,但是可能会存在要取的String的hash值和我们加入的对象的hash值相同。可能会导致取的时候带来额外的比较,降低执行效率。

Stack vs Vector

    我们再来看看Stack和Vector。(关于Stack和Vector的详细实现讨论可以见此文章)从我们的理解来看,其实Stack和Vector之间并不是一个is a的关系。首先一个,我们理解的Stack是一个先入后出的结构。也就是说,我们要添加一个元素到Stack里,只能通过push操作,而要移除某个元素的时候,需要等它上面的元素都pop出去了才能操作。而Vector是什么呢?本质上就相当于一个线程安全的可变长数组。对于一个数组我们可以随机的操作其中的元素。但是如果对Stack这么来的话就有点不太合适了。

    通过前面这两个jdk里的实现我们看到了,在这种使用composition的情况下,我们只是部分的需要使用到其他类的特性。如果直接去继承的话会带来不必要的麻烦。

总结

    继承有风险,用时需谨慎。这就是为什么在OO设计原则里会这么提。所以我们在考虑一些对象之间的关系时,可以这样来看。继承就相当于你做了某个人家的孩子,那么你就要继承他们给定的姓氏以及财富等。对于他们所有的这些限制对于你是必须有效的。如果对于父类的所有特性对于子类不能适用,那就要出大事了。而对于composition来说,则好比是你的一个好朋友。你需要人家的一部分帮助或者特性,但不是全盘照搬。虽然有很多事都可以赖人家去做,自己只要说一声就可以。

参考材料

effective java

openjdk

 

1
3
分享到:
评论

相关推荐

    AdapterDelegates, 用于RecyclerView适配器的"Favor composition over inheritance".zip

    AdapterDelegates, 用于RecyclerView适配器的"Favor composition over inheritance" AdapterDelegates阅读这个项目的动机在我的博客文章。依赖项这里库在 Maven 中心可用:compile '...

    powerbuilder10应用基础及实例分析

    《PowerBuilder 10 应用基础及实例分析》是一本深入探讨PowerBuilder 10这一强大开发工具的书籍。PowerBuilder是Sybase公司推出的一种可视化、面向对象的数据库应用程序开发工具,尤其在企业级应用中有着广泛的应用...

    Android代码-YaMvp

    YaViewDelegate: Composition over inheritance compile 'com.github.piasy:YaMvp:1.3.1' YaMvp-Rx RxJava Subscription management. compile 'com.github.piasy:YaMvp-Rx:1.3.1' YaRxDelegate: Comp

    inheritance

    【标题】"inheritance"指的是在编程领域中的继承机制,特别是在面向对象编程(OOP)的概念中。继承是面向对象编程的一个核心特性,允许一个类(子类或派生类)从另一个类(父类或基类)继承属性和方法。这种设计模式...

    Inheritance

    `,分别输出书籍页数和字典定义的数量及每页定义的平均数。 继承的一大优势在于软件复用。通过利用已经设计、实现和测试过的软件组件,我们可以构建新的类,从而节省大量的工作。然而,需要注意的是,继承是单向的...

    AdapterDelegates:RecyclerView Adapters的“继承中的重要组成”

    Favor composition over inheritance 想法是为每种视图类型定义一个AdapterDelegate。 该委托负责创建ViewHolder并为特定视图类型绑定ViewHolder。 然后,您可以通过注册您真正需要的AdapterDelegates来组成...

    inheritance---derived-class.rar_inheritance

    在C++编程语言中,继承(Inheritance)是面向对象编程的一个核心概念,它允许一个类(称为子类或派生类)从另一个类(称为基类或父类)继承特性。这种特性使得代码重用变得简单,同时也能实现多态性。"inheritance--...

    Android代码-AdapterDelegates-ListView

    Based on https://github.com/sockeqwe/AdapterDelegates." Favor composition over inheritance" for ListView Features Composable view handling on ListView Plugable data handler ViewHolder pattern for ...

    Android代码-Android 上漂亮的 Dialog 效果

    &gt; Favor composition over inheritance. LovelyDialog doesn't subclass any Dialog related classes, it is just a lightweight extensible wrapper for Dialog and manipulations with custom view. If you would ...

    Advanced JavaScript (closures,prototype,inheritance)

    JavaScript,作为一种广泛应用于Web开发的脚本语言,其高级特性如闭包(closures)、原型(prototype)和继承(inheritance)是理解其精髓的关键。本文将深入探讨这些概念,帮助开发者更好地掌握JavaScript的核心。 ...

    ACE-inheritance

    本篇文章将深入探讨ACE中的关键类及其继承关系,帮助读者理解ACE内部的工作原理及设计模式。 #### ACE Event Handler体系 ACE的核心是`ACE_Event_Handler`类,几乎所有与I/O操作相关的类都直接或间接地继承自它。...

    EC.zip_eC_inheritance

    How do you choose between inheritance and templates? Between templates and generic pointers? Between public and private inheritance? Between private inheritance and layering? Between function ...

    La-POO-con-Java.rar_inheritance

    Basic manual of java for beginners with simple and easy descriptions. Inheritance, composition, collections, files, interfaces

    Topic 10 Inheritance.md

    Topic 10 Inheritance.md

    Canalization of Development and the Inheritance of Acquired Characters

    #### 1.3 实例分析 - **果蝇实验**:沃丁顿通过对果蝇进行不同温度下的培养实验,观察到在宽泛的温度范围内,果蝇的翅膀形态保持不变,这表明渠化的存在。 - **遗传机制**:研究表明,渠化可能与基因表达调控有关。...

    Swift 2 by Example(PACKT,2016)

    it introduces new ways to solve old problems, more robust error handling, and a new programming paradigm that favours composition over inheritance. Swift 2 by Example is a fast-paced, practical ...

    C 程序设计教学课件:CHAPTER 8 INHERITANCE.ppt

    7. **8.7 Virtual Inheritance** - 虚继承(Virtual Inheritance)用于解决多重继承中的菱形问题,确保基类的同一成员在派生类中只有一个实例。 在实际编程中,理解并灵活运用这些概念对于构建高效、可维护的C++...

    package-and-inheritance.zip_inheritance

    类是创建对象的模板,而对象是类的实例。类定义了对象的状态(属性)和行为(方法)。 2. **异常处理**:Java提供了强大的异常处理机制,通过try-catch-finally语句块来捕获和处理程序运行时可能出现的错误,确保...

Global site tag (gtag.js) - Google Analytics