优先使用对象组合,而不是类继承
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”
原因
- 继承在某种程度上破坏了封装性,子类父类耦合度过高
- 而对象组合只要求被组合的对象具有良好定义的外部接口,耦合度相对较低
- 子类不需要这么多的方法(继承其实就是复制,会导致方法冗余),并且父类的方法可能有害
- 子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用、甚至有害的父类方法。
- 换句话说,子类只希望继承父类的一部分方法,怎么办?
- 多重继承会导致类之间的关系非常庞杂
- 实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀
- 在类关系的确定时期中,组合的灵活性大
- 继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况
- 而组合却可以比继承灵活很多,可以在运行期才决定某个对象
背景:
- 类继承在面向对象初期,大概是在80年代末90年代初的时候,非常受人推崇。包括面向对象的初学者也很喜欢用类继承,觉得继承才是面向对象的灵魂核心。
- 这可能是来自于父父子子,和我们人类的繁衍关系很像,所以对继承有一种来自于这种层面的,莫名其毛的好感。
- 但是这个好感是非常错误的。其实继承关系更像是一种隶属关系,如动物和生物之间、人类和动物之间、汽车和交通工具之间,这才是比较准确的继承关系。并不是像父子之间的那种关系。所以很多类型都不具有继承的关系,因此经常会出现误用继承的情况。
【例子】父类的方法给子类带来的危害,使得子类达不到设计目的,甚至很难找出bug
现在我们需要这样一个HashMap,它除了能按常规Map那样取值,如get(Object obj)
。还能按位取值,像ArrayList那样,按存入对象的先后顺序取值。
public class ListMap extends HashMap{
private List list;
public ListMap {
super();
this.list = new ArrayList();
}
public Object put(Object key, Object value) {
if(list.contains(key)) list.remove(key);
this.list.add(key);
return super.put(key, value);
}
public Object getKey(int i) {
return this.list.get(i);
}
public Object getValue(int i) {
return this.get(getKey(i));
}
public int size() {
return this.list.size();
}
}
//使用
ListMap map = new ListMap();
map.put("a", "111");
map.put("v", "190");
map.put("d", "132");
String[] list = (String[])map.values().toArray(new String[0]);
for(int i=0; i<list.length; ++i)
System.out.println(list[i]);
//输出:和放入的顺序的不一样,输出的应该是111、190、132
132
111
190
//问题在于,没有使用map.getValue来得到结果
//而使用的是ListMap父类HashMap中的values函数所取得的
//如此产生了问题,而这种问题也是很难被察觉的
组合方式可以避免这个问题
public class MyListMap{
private HashMap map;
private List list;
public MyListMap() {
this.map = new HashMap();
this.list = new ArrayList();
}
public Object put(Object key, Object value) {
if (list.contains(key)) list.remove(key);
this.list.add(key);
return this.map.put(key.value);
}
public Object getKey(int i) {
return this.list.get(i);
}
public Object getValue(int i) {
return this.map.get( getKey(i) );
}
public int size() {
return this.list.size();
}
}