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与其他模块进行通信.这个概念称为信息隐藏 ...
随机推荐
- Core Animation一些Demo总结 (动态切换图片、大转盘、图片折叠、进度条等动画效果)
前一篇总结了Core Animation的一些基础知识,这一篇主要是Core Animation 的一些应用,涉及到CAShapeLayer.CAReplicatorLayer等图层的知识. 先看效果 ...
- Cubieboard2裸机开发之(三)C语言操作LED
前言 前面通过汇编语言点亮LED,代码虽然简单,但并不是很直观.这次使用熟悉的C语言来控制LED,但是需要注意的地方有两点,第一,要想使用C语言,首先需要在调用C语言代码之前设置好堆栈:第二,调用C语 ...
- C用函数指针模拟重载 C++重载
C中为什么不支持重载,即同一作用域内不允许出现同名函数? 我们都知道重载是c++面向对象的特性.c语言中是不存在的.所谓重载简单来说就是一个函数名可以实现不同的功能,要么输入参数不同或者参数个数不同, ...
- Windows Store 开发总结——文件操作
1.读取Isolated Storage 每个Metro程序都有三个文件夹:Local,Roaming,Temp.每个文件夹的访问方法都是相同的. Local用于将数据存储在本地,这是程序特定的文件夹 ...
- [LeetCode] Remove Invalid Parentheses
This problem can be solved very elegantly using BFS, as in this post. The code is rewritten below in ...
- SGU 422 Fast Typing(概率DP)
题目大意 某人在打字机上打一个字符串,给出了他打每个字符出错的概率 q[i]. 打一个字符需要单位1的时间,删除一个字符也需要单位1的时间.在任意时刻,他可以花 t 的时间检查整个打出来的字符串,并且 ...
- div 等高
padding-bottom: 5000px; margin-bottom: -5000px;
- MSSQL大数据量增加字段耗时对比
单个数据表记录数为1亿4千万条. 一.测试同时增加两个允许为空的字段. ALTER TABLE [dbo].[XRecord] ADD [sType] int,[cType] int GO 开始时间: ...
- mysql ODBC connector相关问题
mysql ODBC connector我安装了,怎么就不成功了 进到命令行,运行下边的:C:\>cd \windows\SysWOW64 C:\Windows\SysWOW64>odbc ...
- 在android中如何通过点击edittext之外的部分使软键盘隐藏
我们知道在android中点击edittext框就会自动弹出软键盘,那怎么通过点击edittext之外的部分使软键盘隐藏呢?(微信聊天时的输入框就是这个效果,这个给用户的体验还是很不错的) 首先我们要 ...