Java - 复合模式优于继承
继承是实现代码重用的方法之一,但使用不当则会导致诸多问题。
继承会破坏封装性,对一个具体类进行跨包访问级别的继承很危险。
即,子类依赖父类的实现细节。
如果父类的实现细节发生变化,子类则可能遭到破坏。
举个例子,扩展HashSet,记录HashSet实例创建以来一共进行了多少次添加元素的操作。
HashSet有两个添加元素的方法——add(E e)和addAll(Collection<? extends E> c)。
那就覆盖这两个方法,在添加操作执行前记录次数:
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方法添加3个元素:
public static void main(String[] args) {
InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
结果是: wKioL1PLrWjBf_fsAADIJ3WV64w978.jpg
导致这种结果的原因很简单。
参考AbstractCollection中的add(E e)和addAll(Collection<? extends E> c),
add(E e)中只有一段throw new UnsupportedOperationException();
而addAll(Collection<? extends E> c)的文档注释中有这么一段话:
* <p>Note that this implementation will throw an
* <tt>UnsupportedOperationException</tt> unless <tt>add</tt> is
* overridden (assuming the specified collection is non-empty).
解决这个问题的方法很简单,只需要去掉覆盖的addAll方法即可。
但这样却不能解决根本问题,即HashSet的addAll方法不保证在以后的发行版本中不发生变化。
即,子类实现依赖父类实现,父类发生变化时子类遭到破坏。
也许我们可以覆盖父类方法重新实现,虽然解决问题,但这样费力不讨好,毫无意义。
另外,父类增加或者移除方法也会对子类产生影响。
举个例子,子类扩展了某个集合类,覆盖了所有添加元素的方法,在添加元素之前对元素进行检查,让所有元素满足某个条件。
如果在后来的版本中,父类增加了新的添加元素的方法,而子类没有覆盖该方法,导致非法元素添加到集合中。
反之,也有可能出现这种情况。
即便父类的实现没有问题,但也可以因为子类实现不当而破坏父类的约束。
比如,父类恰好增加了和子类相同签名和返回类型的方法。
于是,为了应对这些情况,可以使用复合模式(composition)代替继承。
即,在一个forwarding class中增加一个private field引用现有类的实例,forwarding class中的方法对应现有类的方法。
代码如下:
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
public void clear() {
s.clear();
}
public boolean contains(Object o) {
return s.contains(o);
}
public boolean isEmpty() {
return s.isEmpty();
}
public int size() {
return s.size();
}
public Iterator<E> iterator() {
return s.iterator();
}
public boolean add(E e) {
return s.add(e);
}
public boolean remove(Object o) {
return s.remove(o);
}
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
@Override
public boolean equals(Object o) {
return s.equals(o);
}
@Override
public int hashCode() {
return s.hashCode();
}
@Override
public String toString() {
return s.toString();
}
}
使用时直接继承forwarding class:
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 += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
forwarding class通过Set接口提供了相应方法。
这种设计也有其灵活性,继承只能选择Set的某个特定实现,但使用复合我们可以选择任何接口实现。
比如:
public static void main(String[] args) {
InstrumentedSet<String> s = new InstrumentedSet<String>(
new HashSet<String>());
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());
}
如何从继承和复合之间做出选择?
比较抽象的说法是,只有子类和父类确实存在"is-a"关系的时候使用继承,否则使用复合。
或者比较实际点的说法是,如果TypeB只需要TypeA的部分行为,则考虑使用复合。
Java - 复合模式优于继承的更多相关文章
- Java复合优先于继承
复合优于继承 继承打破了封装性(子类依赖父类中特定功能的实现细节) 合理的使用继承的情况: 在包内使用 父类专门为继承为设计,并且有很好的文档说明,存在is-a关系 只有当子类真正是父类的子类型时,才 ...
- Effective Java 第三版——18. 组合优于继承
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- java 装饰者模式与继承的区别
装饰者模式目标 把许多要实现的功能,加载在子类上,类的继承,显得很臃肿,装饰着模式是在不改变原有类文件和使用继承的情况下,通过创建一个包装对象动态地扩展一个对象的功能,相比生成子类更为灵活 装饰者模式 ...
- Java 设计模式_复合模式(2016-08-31)
一.什么是复合模式? 在形式上,复合模式确实是多个模式的组合,但满足了这一条并不一定是复合模式,注意它的定义: 将多个模式结合起来形成一个“框架”,以解决一般性问题 一提到“框架”,可能最容易联想到的 ...
- java设计模式----复合模式
复合模式:复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题 要点: 1.MVC是复合模式,结合了观察者模式.策略模式和组合模式 2.模型使用观察者模式,以便观察者更新,同时保持 ...
- <代码整洁之道>、<java与模式>、<head first设计模式>读书笔记集合
一.前言 几个月前的看书笔记 ...
- Java与模式读书笔记
>设计目标:可扩展性,灵活性,可插入性. >设计原则 ● Open Closed Principle 开闭原则 对扩展开放,对修改关闭. 对面向对象的语言来说,不可以更改的是系统的抽象层, ...
- 设计模式学习--复合模式(Compound Pattern)
设计模式学习--复合模式(Compound Pattern) 概述 ——————————————————————————————————————————————————— 2013年8月4日<H ...
- Java并发编程阅读笔记-Java监视器模式示例
1.前言 书中在解释Java监视器模式的时候使用了一个车辆追踪器例子,根据不同的使用场景给出了不同的实现和优化. 2.监视器模式示例 实现一个调度车辆的"车辆追踪器",每台车使用一 ...
随机推荐
- 手动开发PHP模板引擎 一 (35)
模板叫做TPL,模仿于smarty模板引擎. 我们所说的模板是Web模板,是主要由HTML标记组成的语言来编写的页面,但也有如何表示包含动态生成内容的方式(解析标签).模板引擎是一种软件库,允许我们从 ...
- 486. Predict the Winner
Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from eith ...
- thinkphp3搜索结果分页
公司的办公室搬到武昌,办公室水不好喝 还是乐百氏 娃哈哈的水我们oa用的tp3的一套oa,现在boss要求按状态 和类型(2个维度)来搜索案子 数量多,用 分页注意到初始的表单 input的name ...
- 基于scrapy-redis两种形式的分布式爬虫
redis分布式部署 1.scrapy框架是否可以自己实现分布式? - 不可以.原因有二. 其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls ...
- PHP中运算符优先级
运算符优先级指定了两个表达式绑定得有多“紧密”.例如,表达式 1 + 5 * 3 的结果是 16 而不是 18 是因为乘号(“*”)的优先级比加号(“+”)高.必要时可以用括号来强制改变优先级.例如: ...
- google Agent
一句话:改HOSTS文件 有时候要查看appengine.google.com的后台,但死活上不去,怎么办?还是那个老办法: 1.在cmd下Ping www.google.com.hk,获得IP地址, ...
- C#-MVC-强数据类型、TempData、多表单、ajax
一.强数据类型 将某一个或一组数据在控制器传递到视图上去 一个视图里只能有一个强类型数据 强类型数据 - 将某一个或一组数据在控制器传递到视图上去,同ViewBag,数据更稳定,防止多数据传递中出现错 ...
- C#-语言基础+数据类型+运算符
一.C#语言基础 新建项目:文件→新建→项目→Visual C#(默认.NET Framework 4.5)→控制台应用程序 1.项目结构 (1)项目后缀 .config ——配置文件(存放配置参数文 ...
- win10 安装 mysql 5.7
win10 安装 mysql 5.7 1.在mysql 官网下载mysql 5.7 的手动安装包 mysql--winx64.zip 2. 解压到mysql 目录的文件夹下面 D:\moudle\my ...
- Mac 10.12安装SVN工具SmartSVM 7.6
说明:SVN工具没有最好的,只有用的最顺手的. 下载: (链接: https://pan.baidu.com/s/1dFGqEsT 密码: uyjx)