复合优于继承

继承打破了封装性(子类依赖父类中特定功能的实现细节)

合理的使用继承的情况:

  • 在包内使用
  • 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系

只有当子类真正是父类的子类型时,才适合用继承。

对于两个类A和B,只有两者之间存在"is-a"关系,类B才能拓展类A。

继承机制会把父类API中的所有缺陷传播到子类中,而复合允许设计新的API来隐藏这些缺陷。

复合(composition):不扩展现有的类,而是在新的类中增加一个私有域,引用现有类的一个实例。

转发(fowarding):新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回结果。

 public class FowardSet<E> implements Set<E> {   #转发类,被装饰类

     //引用现有类的实例,增加私有域
private final Set<E> set; public FowardSet(Set<E> set){
this.set = set;
} /*
*转发方法
*/
@Override
public int size() {
return set.size();
} @Override
public boolean isEmpty() {
return set.isEmpty();
} @Override
public boolean contains(Object o) {
return set.contains(o);
} @NotNull
@Override
public Iterator<E> iterator() {
return set.iterator();
} @NotNull
@Override
public Object[] toArray() {
return set.toArray();
} @NotNull
@Override
public <T> T[] toArray(T[] a) {
return set.toArray(a);
} @Override
public boolean add(E e) {
return set.add(e);
} @Override
public boolean remove(Object o) {
return set.remove(o);
} @Override
public boolean containsAll(Collection<?> c) {
return set.containsAll(c);
} @Override
public boolean addAll(Collection<? extends E> c) {
return set.addAll(c);
} @Override
public boolean retainAll(Collection<?> c) {
return set.retainAll(c);
} @Override
public boolean removeAll(Collection<?> c) {
return set.removeAll(c);
} @Override
public void clear() {
set.clear();
} @Override
public boolean equals(Object obj) {
return set.equals(obj);
} @Override
public String toString() {
return set.toString();
} @Override
public int hashCode() {
return set.hashCode();
}
} /*
* 包装类(wrapper class),采用装饰者模式
*/
public class InstrumentedSet<E> extends FowardSet<E> {
private int addCount=0; public InstrumentedSet(Set<E> set) {
super(set);
} @Override
public boolean add(E e) {
addCount++;
return super.add(e);
} @Override
public boolean addAll(Collection<? extends E> c) {
addCount+=c.size();
return super.addAll(c);
} public int getAddCount() {
return addCount;
}
}

上面的例子中,FowardingSet是转发类,也是被包装类,而InstrumentedSet是包装类,它采用的是装饰者模式,而不是委托模式。

装饰者模式:

装饰者模式挺像一种组合、而且是可以任意搭配、制定的。当我们有新的需求的时候、添加一个装饰器就ok。必要的时候可以添加组件、这样就实现了不用修改现有代码就可以扩展和修改新的功能的一个目的。还是那个设计原则——open for extension, close for modification.

动态地将责任附加到对象上.若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
装饰者和被装饰者之间必须是一样的类型,也就是要有共同的超类。在这里应用继承并不是实现方法的复制,而是实现类型的匹配。因为装饰者和被装饰者是同一个类型,因此装饰者可以取代被装饰者,这样就使被装饰者拥有了装饰者独有的行为。根据装饰者模式的理念,我们可以在任何时候,实现新的装饰者增加新的行为。如果是用继承,每当需要增加新的行为时,就要修改原程序了。

尽量使功能独立拆分解耦,每种新的功能分离开来称为一个装饰者,当需要的时候,就可以组合在一起使用,而不是像单独使用继承那样将多个功能耦合在一起,阅读和使用不方便,并且不利用扩展,比如 有接口A 有功能1 2 3 4 5 如果单单使用继承,那么为了使结构符合面向对象编程,将会组合成10个子类,当功能进一步扩展的时候,数量时恐怖的,并且将是很难被使用者记忆和理解的。当我们使用了装饰者模式之后,仅仅需要实现数个装饰者,然后根据需要进行组合使用就可以了。

包装类不适合用在回调框架(callback framework)中,会出现SELF问题

在回调框架中,对象把自身的引用传递给其他对象,用于后续的调用(回调)

SELF问题:被包装的对象并不知道它外面的包装对象,所以它传递一个指向自身的引用(this),回调时却避开了外面的包装对象。

简而言之,继承的功能非常强大,但也存在诸多问题,因为违背了封装原则。只有当子类和超类确实存在子类型关系时,使用继承才是恰当的,但如果子类和超类在不同包中,并且超类并不是为了继承而设计的,那么继承会导致脆弱性,为了避免这种脆弱性,可以用符合和转发机制来代替继承,尤其是当存在适当的接口实现包装类的时候。包装类不仅比子类更加健壮,而且功能更加强大。

如何从继承和复合之间做出选择?
比较抽象的说法是,只有子类和父类确实存在"is-a"关系的时候使用继承,否则使用复合。
或者比较实际点的说法是,如果子类只需要实现超类的部分行为,则考虑使用复合。

Java复合优先于继承的更多相关文章

  1. EffectiveJava——复合优先于继承

    继承时实现代码重用的重要手段,但它并非永远是完成这项工作的最佳工具,不恰当的使用会导致程序变得很脆弱,当然,在同一个程序员的控制下,使用继承会变的非常安全.想到了很有名的一句话,你永远不知道你的用户是 ...

  2. EffectiveJava(16)复合优先于继承

    为什么复合优先于继承? 1.继承违反了封装原则,打破了封装性 2.继承会不必要的暴露API细节,称为隐患.比如通过直接访问底层使p.getProperty(K,V)的键值对可以不是String类型 3 ...

  3. Java - 复合模式优于继承

    继承是实现代码重用的方法之一,但使用不当则会导致诸多问题. 继承会破坏封装性,对一个具体类进行跨包访问级别的继承很危险. 即,子类依赖父类的实现细节. 如果父类的实现细节发生变化,子类则可能遭到破坏. ...

  4. java面向对象(封装-继承-多态)

    框架图 理解面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是一种思想 面向过程强调的是功能行为 面向对象将功能封装进对象,强调具备了功能的对象. 面向对象是基于面向过程的. 面向对象的特点 ...

  5. Java三大特性(封装,继承,多态)

    Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...

  6. Java之封装,继承,多态

    一,前言 ​ 今天总结一下关于Java的三大特性,封装,继承,多态.其实关于三大特性对于从事编程人员来说都是基本的了,毕竟只要接触Java这些都是先要认识的,接下来就系统总结一下. 二,封装 ​ 先来 ...

  7. 【Java】面向对象之继承

    多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可.其中如图中所示,食草动物.食肉动物.兔子.羊.狮子.豹都可以称为子类,动物类称为父 ...

  8. Java 学习笔记(6)——继承

    之前说过了Java中面向对象的第一个特征--封装,这篇来讲它的第二个特征--继承.一般在程序设计中,继承是为了减少重复代码. 继承的基本介绍 public class Child extends Pa ...

  9. Java:类与继承

    Java:类与继承 对于面向对象的程序设计语言来说,类毫无疑问是其最重要的基础.抽象.封装.继承.多态这四大特性都离不开类,只有存在类,才能体现面向对象编程的特点,今天我们就来了解一些类与继承的相关知 ...

随机推荐

  1. 《高性能javascript》阅读摘要

    最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践.性能优化类的书.记录下主要知识. 加载和执行 脚本位置 放在<head>中的javasc ...

  2. FCC 成都社区·前端周刊 第 7 期

    01. ES2016, 2017, 2018 中的新特性 文章介绍了 18 个 ECMAScript 2016,2017 和 2018 中新增加的特性,这些特性已被加入到 TC39 提案中.包括Arr ...

  3. 数据结构-ST表

    数据结构-ST表 不可修改,在线查询的 RMQ 问题. 其中 \(f[i][j]\) 表示 \(i\sim i+(1<<j)-1\) 这段的 RMQ 值. 时间复杂度 \(O(n\log ...

  4. 网络流媒体协议的联系与区别(RTP RTCP RTSP RTMP HLS)

    目录 网络流媒体协议的联系与区别(RTP RTCP RTSP RTMP HLS) 简结 RTP RTCP RTSP 区别与联系 RTSP.RTMP.HLS 区别与联系 关于直播 流媒体各协议层次图 基 ...

  5. H5开发移动应用APP(店铺系列一)

    首先,这是个真实的案例,我大兄弟在深圳开汽修店铺,但需要系统来管理日常经营活动,这正不是我擅长的吗? 说干就干,直接后端+web端+移动端来一套,于是紧急赶工,起早摸黑,产出约3万行总量代码,此系统与 ...

  6. SpringBoot2 整合ElasticJob框架,定制化管理流程

    本文源码:GitHub·点这里 || GitEE·点这里 一.ElasticJob简介 1.定时任务 在前面的文章中,说过QuartJob这个定时任务,被广泛应用的定时任务标准.但Quartz核心点在 ...

  7. flask修改flask_wtf使其支持json数据的validation验证

    flask默认是前后端不分离策略,前端通过flask+wtf表单来传递post,put...等数据. 现在前后端分离是趋势,那么对flask进行一定的修改,变为前后端分离,在前端页面中请求后端,那么请 ...

  8. 自定义添加$_SERVER中的变量

    如何根据自己项目中的需求自定义$_SERVER中的变量呢?比如$_SERVER['XU_TEXT_PARAM'],这个超全局变量输出肯定是会报错的.那如何自定义它,让它输出我们想要输出的值呢? 1.在 ...

  9. 如何找回微信小程序源码?2020年微信小程序反编译最新教程 小宇子李

    前言:在网上看了找回微信小程序源码很多教程,都没法正常使用.微信版本升级后,会遇到各种报错, 以及无法获取到wxss的问题.查阅各种资料,最终解决,于是贴上完整的微信小程序反编译方案与教程. 本文章仅 ...

  10. 关于Resouces.resx 在WPF中{x:Static}不显示内容只显示字段的问题解决办法

    问题现象:<object property="{x:Static prefix:typeName.staticMemberName}" .../> 界面中只显示资源引用 ...