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

于方法调用不同的是,继承打破的封装性。换句话说,子类依赖于其超类中特定功能的实现细节。超类的实现有可能随着发行版本的不同而变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有修改过。因而,子类有必要随着超类的更新而演变。

为了说明的更加具体一些,假设有一个程序使用了HashSet,想要查询它被创建以来添加了多少个元素,编写一个hashSet变量,它记录下试图插入的元素数量,并有一个访问数量的方法

/**
* 复合优先与继承
* @author weishiyao
*
* @param <E>
*/
// Broken - Inappropriate use of inheritance
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0; public InstrumentedHashSet() {
} public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
} @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;
}
}

  看起来非常合理,但是不能正常工作,假设创建一个实例用addAll方法添加三个元素,

	public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList("a", "b", "c"));
System.out.println(s.getAddCount());
}

  这时候我们期望的返回值应该是3,但是事与愿违,结果是6,因为在HashSet内部,addAll方法是机遇add方法来实现,相当于每次插入我们都计算了2次,所以结果是6.

  这个问题来源于Override这个动作,但是如果我们不覆盖现有的方法,可能认为是安全的,虽然这种做法比较安全一些,但是,也并非没有风险,如果超类在后续版本中增加了一个新的方法,并且不幸的是,这个方法如果和我们写的方法重名,要么是返回类型不同,这样整个程序将会直接down掉,如果返回类型相同,问题返回上面。

  这时候有一种方案可以避免以上的所有问题。不用扩展现有的类,而是在新的类中增加一个私有对象,它引用现有对象的一个实例,这种设计被称作为复合,因为现有的类变成了新类的一个组件,新类中的每个实力方法都可以调用被包涵的现有类实例中对应的方法,并返回它的结果,这称为转发,新类中的方法被称为转发方法。

  重新实现上面那个需求

/**
* 复合优先与继承
* @author weishiyao
*
* @param <E>
*/
// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> { private final Set<E> s; public ForwardingSet(Set<E> s) {
this.s = s;
} @Override
public int size() {
return s.size();
} @Override
public boolean isEmpty() {
return s.isEmpty();
} @Override
public boolean contains(Object o) {
return s.contains(o);
} @Override
public Iterator<E> iterator() {
return s.iterator();
} @Override
public Object[] toArray() {
return s.toArray();
} @Override
public <T> T[] toArray(T[] a) {
return s.toArray(a);
} @Override
public boolean add(E e) {
return s.add(e);
} @Override
public boolean remove(Object o) {
return s.remove(o);
} @Override
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
} @Override
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
} @Override
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
} @Override
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
} @Override
public void clear() {
s.clear();
} }

  

/**
* 复合优先与继承
* @author weishiyao
*
* @param <E>
*/
// Wrapper class - uses composition in place of inheritance
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0; public InstrumentedSet(Set<E> s) {
super(s);
} @Override
public boolean add(E e) {
addCount++;
return super.add(e);
} @Override
public boolean addAll(Collection<? extends E> c) {
addCount++;
return super.addAll(c);
} public int getAddCount() {
return addCount;
}
}

  Set接口的存在使得InstrumentedSet类的设计成为可能,因为Set接口保存了HashSet类的功能特性。除了获得健壮性外,这种设计也带来了灵活性。InstrumentedSet类实现了Set接口,并拥有单个构造器,它的参数也是Set类型,从本质上来将,这个类把一个Set转变成了另一饿Set,同时增加了计数的功能。

  前面提到的基于继承的方法只适用与单个具体的类,并且对于超类中所支持的每个构造器都要求有一个单独的构造器,于此不同的是这里的包装类可以用来包装任何Set实现,并且可以结合任何以前存在的构造器一起工作。例如:

Set<Date> s1 = new InstrumentedSet<>(new TreeSet<Date>());
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>());

  因为每一个InstrumentedSet实例都把另一个Set实例包装起来了,所有InstrumentedSet类被称为包装类。这也正是装饰器模式,因为InstrumentedSet类对一个集合进行了装饰,为它增加了计数特性。

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

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

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

  2. Java复合优先于继承

    复合优于继承 继承打破了封装性(子类依赖父类中特定功能的实现细节) 合理的使用继承的情况: 在包内使用 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系 只有当子类真正是父类的子类型时,才 ...

  3. 【Effective Java】6、使用复合优先于使用继承

    这个,不管是什么书都会这样说,因为常常我们并不需要继承,而只是想把类进行一定的扩展,而我们想扩展的属性或方法对应的类都有,这个时候如果两者是is a的关系,这种关系是确实存在的,那么就可以使用继承,不 ...

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

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

  5. <EffectiveJava>读书笔记--01继承的使用注意

    1, 父类的构造器方法中不能调用能够被子类重写的方法. 分析: 当初始化一个子类时, 首先要初始化父类, 即调用父类的构造方法; 如果父类的构造方法中调用了可被重写的其它方法, 那么此时调用的其实是该 ...

  6. Chapter 02:复合 VS 继承

    复合优先于继承,继承是实现代码重用的有力手段,并不是所有情况都适用,使用不当会导致软件变得很脆弱.与方法调用不同的是,继承打破了封装性. 总而言之,组合和继承,都能实现对类的扩展.但是要分具体情况用哪 ...

  7. 为什么说JAVA中要慎重使用继承

    JAVA中使用到继承就会有两个无法回避的缺点: 打破了封装性,迫使开发者去了解超类的实现细节,子类和超类耦合. 超类更新后可能会导致错误. 继承打破了封装性 关于这一点,下面是一个详细的例子(来源于E ...

  8. 为什么说JAVA中要慎重使用继承 C# 语言历史版本特性(C# 1.0到C# 8.0汇总) SQL Server事务 事务日志 SQL Server 锁详解 软件架构之 23种设计模式 Oracle与Sqlserver:Order by NULL值介绍 asp.net MVC漏油配置总结

    为什么说JAVA中要慎重使用继承   这篇文章的主题并非鼓励不使用继承,而是仅从使用继承带来的问题出发,讨论继承机制不太好的地方,从而在使用时慎重选择,避开可能遇到的坑. JAVA中使用到继承就会有两 ...

  9. Effective java笔记(三),类与接口

    类与接口是Java语言的核心,设计出更加有用.健壮和灵活的类与接口很重要. 13.使类和成员的可访问性最小化 设计良好的模块会隐藏起所有的实现细节,仅使用API与其他模块进行通信.这个概念称为信息隐藏 ...

随机推荐

  1. 【Cocos2d-Js基础教学 入门目录】

    本教程视地址频在: 九秒课堂 完全免费 从接触Cocos2dx-Js以来,它的绽放的绚丽让我无法不对它喜欢.我觉得Js在不断带给我们惊喜:在开发过程中,会大大提升我们对原型开发的利用率,使用Js语言做 ...

  2. Hadoop 2.4.1 登录认证配置小结

    1.简单模式 这种模式,配置简单,使用简单. core-site.xml添加 <property> <name>hadoop.security.authorization< ...

  3. BZOJ 1006 [HNOI2008] 神奇的国度(简单弦图的染色)

    题目大意 K 国是一个热衷三角形的国度,连人的交往也只喜欢三角原则.他们认为三角关系:即 AB 相互认识,BC 相互认识,CA 相互认识,是简洁高效的.为了巩固三角关系,K 国禁止四边关系,五边关系等 ...

  4. Quiz(贪心,快速幂乘)

    C. Quiz time limit per test 1 second memory limit per test 256 megabytes input standard input output ...

  5. 【转载】 IE/Firefox每次刷新时自动检查网页更新,无需手动清空缓存的设置方法

    [参考了别人的文章]我们做技术,经常在写页面的时候需要多次刷新测试,可是浏览器都有自己的 缓存机制,一般CSS和图片都会被缓存在本地,这样我们修改的CSS就看不到效果 了,每次都去清空缓存,再刷新看效 ...

  6. Java知多少(完结)

    系列文章: Java知多少(上) Java知多少(中) Java知多少(下)

  7. Unity3D内置着色器

    Unity内部提供了一些可以直接使用的着色器,这些内置着色器包括以下6个方面: (1)Performance of Unity shaders 着色器的性能和两个方面有关:shader本身和rende ...

  8. 【推荐】iOS汉字转拼音第三方库

    PinYin4Objc是一个在git汉字转拼音的开源库,支持简体和繁体中文.效率POAPinyin等其他库要高,转换库也完整下面简单介绍 实现原理 使用unicode_to_hanyu_pinyin. ...

  9. GPUImage滤镜之锐化

    应用锐化工具可以快速聚焦模糊边缘,提高图像中某一部位的清晰度或者焦距程度,使图像特定区域的色彩更加鲜明. 在应用锐化工具时,若勾选器选项栏中的“对所有图层取样”复选框,则可对所有可见图层中的图像进行锐 ...

  10. [ShortCut] Visual Studio快捷键

    msdn官方快捷键说明:https://msdn.microsoft.com/zh-cn/library/da5kh0wa.aspx 测试工具: visual studio 2013 操作步骤: 1. ...