在系统分析设计这门课中某王就不停地强调一点,就是ISA(is a 原则)。都知道继承是面向对象的一大要素,但是哪里使用复合哪里使用继承,其实还是有讲究的。
可以简单的用ISA原则概括。有一个具备一定功能类,我们要对其功能进行拓展,到底是采用复合呢还是继承呢?当新类与旧类的关系是从属关系是,即cat is an animal,English book is a book,我们优先使用继承;当新类是旧类的组成部分之一时,即hand is a part of body,jiangsu is a part of China,我们优先使用复合。
理由如下:当我们要扩展一个类时,特别是一个别人写好的类,一个类库的类,我们往往关心的仅仅是单个api的功能,而不关心他的实现,但是存在的一个问题就是,同一个类的各个方法直接可能存在联系,可能一个方法的实现依赖于另一个方法,这就意味着,当我们调用一个我们想要操作的方法时,“继承”会隐式的调用另一个方法,这就可能存在问题。
经典的例子是Set中add()和addAll()的内在联系。
需求:新建一个集合类,维护一个addCount变量,记录,一共添加了多少次新值。分别用继承,复合实现。
先看继承:
MySet.java
package cczu.edu.test2;
import java.util.Collection;
import java.util.HashSet;
public class MySet<E> extends HashSet<E>{
private int addCount = 0;
public MySet() {
}
@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;
}
}
Test
@Test
public void test1(){
MySet<String> set = new MySet<String>();
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("def");
list.add("ghi");
set.addAll(list);
System.out.println(set.getAddCount());
//the ans is 6
}
因为,在hashSet的addAll()实现中,是循环调用add()方法的,所以导致3*2。当然你也可以重写addAll()方法,但是这样就失去了继承的意义。
使用复合:
ForwardingSet.java
package cczu.edu.test2;
import java.util.Collection;
import java.util.HashSet;
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 ForwardingSet(){
this.s = new HashSet<E>();
}
@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) {
// TODO Auto-generated method stub
return false;
}
@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();
}
}
Myset2.java
package cczu.edu.test2;
import java.util.Collection;
public class MySet2<E> extends ForwardingSet<E>{
private int addCount = 0;
@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;
}
}
Test
@Test
public void test2(){
MySet2<String> set = new MySet2<String>();
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("def");
list.add("ghi");
set.addAll(list);
System.out.println(set.getAddCount());
//the ans is 3
}
增加一个ForwardingSet.java,作用仅仅就是实现转发。
回过头来看,复合可以将旧类完全地包裹起来,以至于我们不需要关注旧类的实现,同时达到可以调用旧类方法的效果,避免了旧类各个方法之间存在的联系,再加入旧类如果还有其他的权限问题,复合也可以隐藏缺陷。
自我总结:其实设计模式中例如代理模式,适配器模式,装饰器模式,和继承、复合都有些联系,以至于我都有一点混淆,毕竟,设计模式是死的,思想是活的。