七大原则之 组成优于继承原则
- 组成优于继承原则
继承就是一个类继承另一类
我们已经知道类和类之间有3中
a. 继承
b. 依赖
c. 关联
关联可以细问为:
1). 组合 // <<-- 点题了
2). 聚合
所谓的组合模式关系强, 聚合是关系弱
组合优于继承中的组合,其实指的就是关联关系
例子:
- 需求: 制作一个集合,要求该集合能记录曾今加过多少个元素.(不是统计某个时刻中有多少个元素)
class MySet extends HashSet<Object> {
private int count = 0;
@Override
public boolean add(Object o) {
count++;
return super.add(o);
}
@Override
public boolean addAll(Collection c) {
count += c.size();
return super.addAll(c);
}
public int getCount() {
return count;
}
}
public class AppTest {
public static void main(String[] args) {
MySet set = new MySet();
Set<String> set2 = new HashSet<>();
set2.add("葵花宝典");
set2.add("北冥神功");
set2.add("乾坤大罗移");
set.addAll(set2);
int count = set.getCount();
System.out.println(count);
}
}
- 1问题是,在执行了addAll之后,count不是3而是6,为什么呢?因为addAll回调了add方法
- 针对1的问题, addAll会回调add方法,我们修改代码如下, 把addAll删除调, 不要重写父类的HashSet的addAll了
class MySet extends HashSet<Object> {
private int count = 0;
@Override
public boolean add(Object o) {
count++;
return super.add(o);
}
public int getCount() {
return count;
}
}
public class AppTest {
public static void main(String[] args) {
MySet set = new MySet();
Set<String> set2 = new HashSet<>();
set2.add("葵花宝典");
set2.add("北冥神功");
set2.add("乾坤大罗移");
set.addAll(set2);
set.add("独孤九剑");
int count = set.getCount();
System.out.println(count);
}
}
- 此时,这个代码看起来好像很完美,已经满足了需求了
- 2问题是: 目前这个代码,必须依赖这样一个事实,就是HashSet的addAll方法必须去回调add方法
万一在将来的jdk版本中,HashSet的addAll实现代码,突然不再回调add方法了,则在将来的这个jdk版本中,完美自定义的这个MySet就被"撼动" - 比如: HashMap,在jdk1.6 1.7 1.8中, 底层实现分别换了3次!
- 针对b包的问题, MySet必须依赖这样一个事实,addAll必须回掉add,但是jdk未来的版本,不会做这个保证
- 修改代码如下: 我们自己亲自重写addAll,这次重写addAll,不再让count累加size()了,而是保证addAll一定会调用add
class MySet extends HashSet<Object> {
private int count = 0;
@Override
public boolean add(Object o) {
count++;
return super.add(o);
}
@Override
public boolean addAll(Collection<?> c) {
boolean bb = false;
for (Object obj : c) {
if (add(obj)){
bb = true;
}
}
return bb;
}
public int getCount() {
return count;
}
}
public class AppTest {
public static void main(String[] args) {
MySet set = new MySet();
Set<String> set2 = new HashSet<>();
set2.add("葵花宝典");
set2.add("北冥神功");
set2.add("乾坤大罗移");
set.addAll(set2);
set.add("独孤九剑");
int count = set.getCount();
System.out.println(count);
}
}
- 此时,这个代码看起来好像很完美,已经满足了需求了
- 3问题是:
1. 如果再新的jdk版本中,HashSet突然多了一个元素加入集合的入口方法: addSome
这个addSome是我们始料未及的,我们的MySet根本没有重写, 新版本中出现的addSome方法,这样,在这个新版本中,
我们的MySet也继承了addSome方法,当使用addSome方法添加元素时,根本不会去统计元素的数量
2. 我们重写了addAll方法中,和add方法,要知道,在HashSet的所有方法中,难免有一些其它方法,会依赖其它方法,
会依赖于addAll方法和add方法的,我们没头没脑地重写了别人类中的某些方法,就会导致其它依赖这些方法的方法,出现问题!
- 针对3提出的2个问题
- 修改代码如下:
- 我们不再重写add和addAll方法了.
- 我们额外制作两个代替add和addAll的方法: add2 和 addAll2, 还要在类的API文档中说明.每当要使用add和addAll的时候,
都去调用add和addAll的方法,都会调用add2和addAll2
class MySet extends HashSet<Object> {
private int count = 0;
public boolean add2(Object o) {
count++;
return super.add(o);
}
public boolean addAll2(Collection<?> c) {
boolean bb = false;
for (Object obj : c) {
if (add2(obj)){
bb = true;
}
}
return bb;
}
public int getCount() {
return count;
}
}
public class AppTest {
public static void main(String[] args) {
MySet set = new MySet();
Set<String> set2 = new HashSet<>();
set2.add("葵花宝典");
set2.add("北冥神功");
set2.add("乾坤大罗移");
set.addAll2(set2);
set.add2("独孤九剑");
int count = set.getCount();
System.out.println(count);
}
}
- 此时,这个代码看起来好像很勉强,但是也满足了需求了
- 4问题是:
1. 目前这种情况对用户要求有点过分,用户必须看类的api文档,看完了还要特别乖乖的使用add2和addAll2,不能写错
2. 更致命的问题是: 就是那么寸, 在jdk新版本中,HashSet恰恰多了一个api叫做add2和addAll2.
继承,已经尽忠了...
- 针对4提出的2个问题
- 修改代码如下:
- 我们的MySet,再也不要去继承HashSet了.
- 取而代之.我们让MySet和HashSet发生关联关系(组合)
class MySet{
private Set<Object> set = new HashSet<>();
private int count = 0;
public boolean add(Object o) {
count++;
return set.add(o);
}
public boolean addAll(Collection<?> c) {
count += c.size();
return set.addAll(c);
}
public int getCount() {
return count;
}
}
public class AppTest {
public static void main(String[] args) {
MySet set = new MySet();
Set<String> set2 = new HashSet<>();
set2.add("葵花宝典");
set2.add("北冥神功");
set2.add("乾坤大罗移");
set.addAll(set2);
set.add("独孤九剑");
int count = set.getCount();
System.out.println(count);
}
}
- 此时,可以说一声完美
- 组合的优点,我们已经体会到了
- 问题是:
1. 难道以后都不能使用继承了吗?
2. 难道以后都不能进行方法重写了吗?
如果父类作者,和子类的作者,不是同一个人,就别继承.
那么父类作者,不知道未来的子类,会重写自己的哪个方法.
那么子类作者,不知道,未来的父类, 会加入什么新方法
如果父类作者,和子类的作者就是同一个人,那就可以放开手脚使用继承了!
自己当前知道,每个方法都是什么作用,作者可以同时控制父类和子类
我们自己写代码,继承,重写,随便使用.
如果我们仅仅是为了复用代码,而继承别人的类, 难免出现"沟通"上的问题.