复合优于继承

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

合理的使用继承的情况:

  • 在包内使用
  • 父类专门为继承为设计,并且有很好的文档说明,存在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. 不要写很酷但同事看不懂的Java代码

    你好呀,我是沉默王二,一个和黄家驹一样身高,和刘德华一样颜值的程序员.为了提高 Java 编程的技艺,我最近在 GitHub 上学习一些高手编写的代码.下面这一行代码(出自大牛之手)据说可以征服你的朋 ...

  2. 前端每日实战:160# 视频演示如何用纯 CSS 创作一个打开内容弹窗的交互动画

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/GYXvez 可交互视频 此视频是可 ...

  3. 前端每日实战:111# 视频演示如何用纯 CSS 创作一只艺术的鸭子

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/aaoveW 可交互视频 此视频是可 ...

  4. 微信小程序之登录连接django,以及用户的信息授权认证

    小结: 1 如何自定义组件 - 组件和页面一样,也是由四个文件组成,所以我们自定义组件的时候,模拟pages文件夹,把所有的所有的组件都放在一个文件夹中,每个组件又由一个文件夹包裹,方便管理,在对应目 ...

  5. 关于OSS不再维护的一些讨论

    FUSE for macOS 将不再维护 Fuse 是一款针对Mac OS的文件系统所开发的一款开源软件. 用于MacOS的FUSE软件包提供了多个API,用于为OS X 10.9至macOS 10. ...

  6. 这些MongoDB的隐藏操作你真的都掌握了吗?反正我是刚知道

    背景 最近公司系统还原用户时偶尔会出现部分用户信息未还原成功的问题,最为开发人员,最头疼的不是代码存在bug,而是测试发现了bug,但一旦我去重现,它就不见了.Are you kidding me? ...

  7. 学妹问的Spring Bean常用配置,我用最通俗易懂的讲解让她学会了

    你好呀,我是沉默王二,一枚有趣的程序员,写的文章一直充满灵气,力求清新脱俗.昨天跑去王府井的小米店订购了一台小米 10,说是一周之内能到货,但我还是忍不住今天就想见到她.见我茶不思饭不想的,老婆就劝我 ...

  8. spring多数据源分布式事务的分析与解决方案

    一.概述 1.业务背景 对老系统进行重构合并,导致新系统需要同时对3个数据库进行管理.由于出现跨库业务,需要实现分布式事务. 2.开发环境 spring框架版本  4.3.10.RELEASE 持久层 ...

  9. 029.核心组件-Controller Manager

    一 Controller Manager原理 1.1 Controller Manager概述 一般来说,智能系统和自动系统通常会通过一个"控制系统"来不断修正系统的工作状态.在K ...

  10. 全球疫情统计APP图表形式展示

    全球疫情统计APP图表展示: 将该任务分解成三部分来逐个实现: ①爬取全球的疫情数据存储到云服务器的MySQL上 ②在web项目里添加一个servlet,通过参数的传递得到对应的json数据 ③设计A ...