复合优于继承

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

合理的使用继承的情况:

  • 在包内使用
  • 父类专门为继承为设计,并且有很好的文档说明,存在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. idea 报Сannot Run Git runnerw.exe: AttachConsole failed with error 6

    报错:Сannot Run Git runnerw.exe: AttachConsole failed with error 6 解决方案:指向Git 的git.exe文件所在的安装目录,配置上就可以 ...

  2. 20170813-CSRF 跨站请求伪造

    CSRF CSRF是Cross Site Request Forgery的缩写,翻译过来就是跨站请求伪造. 跨站:顾名思义,就是从一个网站到另一个网站. 请求:即HTTP请求. 伪造:在这里可以理解为 ...

  3. js笔记系列之--时间及时间戳

    js入门系列之 时间及时间戳 时间及时间戳 时间及时间戳是js里面很常见的一个概念,在我们写前端页面的时候,经常会遇到需要获取当前时间的情况,所以,了解js中的时间概念非常重要.而时间戳是指格林威治时 ...

  4. 【推荐算法工程师技术栈系列】分布式&数据库--tensorflow

    目录 TensorFlow 高阶API Dataset(tf.data) Estimator(tf.estimator) FeatureColumns(tf.feature_column) tf.nn ...

  5. 必备技能七、Vuex

    这段时间一直在用vue写项目,vuex在项目中也会依葫芦画瓢使用,但是总有一种朦朦胧胧的感觉.于是决定彻底搞懂它. 看了一下午的官方文档,以及资料,才发现vuex so easy! 作为一个圈子中的人 ...

  6. SpringBoot WebSocket STOMP 广播配置

    目录 1. 前言 2. STOMP协议 3. SpringBoot WebSocket集成 3.1 导入websocket包 3.2 配置WebSocket 3.3 对外暴露接口 4. 前端对接测试 ...

  7. html标签及网页语义化理解

    最近重新看了一遍html标签的知识,有很多新的体会,对语义化有了一个新的理解. 那么什么叫做语义化呢,说的通俗点就是:明白每个标签的用途(在什么情况下使用此标签合理)比如,网页上的文章的标题就可以用标 ...

  8. golang Printf 函数有超过 10 个转义字符

    verb 描述 %d 十进制整数 %x, %o, %b 十六进制.八进制.二进制整数 %f, %g, %e 浮点数:如 3.141593, 3.141592653589793, 3.141593e+0 ...

  9. Natas26 Writeup(PHP反序列化漏洞)

    Natas26: 打开页面是一个输入坐标点进行绘图的页面. <html> <head> <!-- This stuff in the header has nothing ...

  10. Postgresql实战经验之alter table 开小差了

    Postgresql实战经验之alter table 开小差了 今天需要将一张有数据的表中一个字段varchar 类型转换为timestamp类型,但是pg的alter table 语句却开小差,出现 ...