EffectiveJava——复合优先于继承
继承时实现代码重用的重要手段,但它并非永远是完成这项工作的最佳工具,不恰当的使用会导致程序变得很脆弱,当然,在同一个程序员的控制下,使用继承会变的非常安全。想到了很有名的一句话,你永远不知道你的用户是如何使用你写的程序的,一个程序员继承另一个程序员写的类也是同样的危险。
于方法调用不同的是,继承打破的封装性。换句话说,子类依赖于其超类中特定功能的实现细节。超类的实现有可能随着发行版本的不同而变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有修改过。因而,子类有必要随着超类的更新而演变。
为了说明的更加具体一些,假设有一个程序使用了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——复合优先于继承的更多相关文章
- EffectiveJava(16)复合优先于继承
为什么复合优先于继承? 1.继承违反了封装原则,打破了封装性 2.继承会不必要的暴露API细节,称为隐患.比如通过直接访问底层使p.getProperty(K,V)的键值对可以不是String类型 3 ...
- Java复合优先于继承
复合优于继承 继承打破了封装性(子类依赖父类中特定功能的实现细节) 合理的使用继承的情况: 在包内使用 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系 只有当子类真正是父类的子类型时,才 ...
- 【Effective Java】6、使用复合优先于使用继承
这个,不管是什么书都会这样说,因为常常我们并不需要继承,而只是想把类进行一定的扩展,而我们想扩展的属性或方法对应的类都有,这个时候如果两者是is a的关系,这种关系是确实存在的,那么就可以使用继承,不 ...
- Java - 复合模式优于继承
继承是实现代码重用的方法之一,但使用不当则会导致诸多问题. 继承会破坏封装性,对一个具体类进行跨包访问级别的继承很危险. 即,子类依赖父类的实现细节. 如果父类的实现细节发生变化,子类则可能遭到破坏. ...
- <EffectiveJava>读书笔记--01继承的使用注意
1, 父类的构造器方法中不能调用能够被子类重写的方法. 分析: 当初始化一个子类时, 首先要初始化父类, 即调用父类的构造方法; 如果父类的构造方法中调用了可被重写的其它方法, 那么此时调用的其实是该 ...
- Chapter 02:复合 VS 继承
复合优先于继承,继承是实现代码重用的有力手段,并不是所有情况都适用,使用不当会导致软件变得很脆弱.与方法调用不同的是,继承打破了封装性. 总而言之,组合和继承,都能实现对类的扩展.但是要分具体情况用哪 ...
- 为什么说JAVA中要慎重使用继承
JAVA中使用到继承就会有两个无法回避的缺点: 打破了封装性,迫使开发者去了解超类的实现细节,子类和超类耦合. 超类更新后可能会导致错误. 继承打破了封装性 关于这一点,下面是一个详细的例子(来源于E ...
- 为什么说JAVA中要慎重使用继承 C# 语言历史版本特性(C# 1.0到C# 8.0汇总) SQL Server事务 事务日志 SQL Server 锁详解 软件架构之 23种设计模式 Oracle与Sqlserver:Order by NULL值介绍 asp.net MVC漏油配置总结
为什么说JAVA中要慎重使用继承 这篇文章的主题并非鼓励不使用继承,而是仅从使用继承带来的问题出发,讨论继承机制不太好的地方,从而在使用时慎重选择,避开可能遇到的坑. JAVA中使用到继承就会有两 ...
- Effective java笔记(三),类与接口
类与接口是Java语言的核心,设计出更加有用.健壮和灵活的类与接口很重要. 13.使类和成员的可访问性最小化 设计良好的模块会隐藏起所有的实现细节,仅使用API与其他模块进行通信.这个概念称为信息隐藏 ...
随机推荐
- mycat配置日志
1: 1: MySql Host is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' ...
- POJ 1631 Bridging signals
Bridging signals Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 9441 Accepted: 5166 ...
- python 教程_【python 基础教程详解】
Lesson 1 准备好学习Python的环境下载的地址是:www.python.org为了大家的方便,我在校内作了copy:http://10.1.204.2/tool/compiler&I ...
- Google首席软件工程师Joshua Bloch谈如何设计一款优秀的API【附PPT】
编者按]随着近来软件规模的日益庞大,API编程接口的设计变的越来越重要.良好的接口设计可以降低系统各部分之间的相互依赖,提高组成单元的内聚性,降低组成单元间的耦合度,从而提高系统的维护性和稳定性. J ...
- IOS8Preview-xCode_6
IOS8Preview-xCode_6 what's new What's new in xCode 6 Xcode 6 introduces a radically new way to desig ...
- Powerdesigner逆向工程从sql server数据库生成pdm (转载)
第一步:打开"控制面板"中的"管理工具" 第二步:点击"管理工具"然后双击"数据源(odbc)" 第三步:打开之后,点击 ...
- Java知多少(108)数据库查询简介
利用Connection对象的createStatement方法建立Statement对象,利用Statement对象的executeQuery()方法执行SQL查询语句进行查询,返回结果集,再形如g ...
- CSS3 垂直居中 左右居中
display: -webkit-box; -webkit-box-orient: horizontal; -webkit-box-pack: center; -webkit-box-align: c ...
- Intel HAXM安装错误处理:(TV-x) is not turned on
Android x86模拟器Intel Atom x86 System Image时提示Intel execute disable bit(xd) is not turned on 运行Elicpse ...
- 疯狂的ASP.NET系列-第一篇:啥是ASP.NET后续
之前总结到了ASP.NET的七大特点,只总结了2大特点,现继续总结后面的5大特点. (3)ASP.NET支持多语言 这里说的多语言就是多种开发语言,如C#,VB.NET,无论你采用哪种开发语言,最终的 ...