前言
对于类的扩展,大家知道有两种方式,一种是:继承和复合。
那么对于继承和复合,二者有什么差别呢?为什么说组合优于继承
正文
组合和继承的区别:
组合 | 继承 |
---|---|
has-a | is-a |
不破坏封装 | 破坏封装,子类依赖父类 |
支持扩展,随意增加组合类 | 只能继承一个父类,必须包含所有方法,增加系统复杂性 |
- 继承是is-a的关系,就像 鸟 是一种 动物。而 组合是has-a的关系,就像 鸟 有 动物的特征。
- 继承破坏了封装性,因为子类依赖父类,而组合则不破坏封装性
- 继承子类只能继承一个父类,同时必须包含所有的方法,而组合支持扩展,可以随意增加组合类。
继承是如何破坏封装性的
有一个通俗的例子,是靖凡大人举的例子:我想拿嘉美的杯子,但是这个杯子是属于嘉美,所以我要把嘉美搬出来,但是嘉美是属于森林,所以我要把森林搬出来……但是我们都忘了,我的初心是“拿嘉美的杯子”。
如何用组合来替换继承
看如下的例子:
public class A {
//方法功能是说话
public void say() {
//一堆说话的实现细节
}
//方法功能是唱歌
public void song() {
//具体的实现细节
// 唱歌的前提要先说话,所以调用自身的say()方法
say();
//然后一堆唱歌的实现细节
}
}
public class B extends A{
@Override
public void say() {
//我就想再说话之前先告诉大家一声我要说话了
System.out.println("我要开始说话了");
super.say();
}
@Override
public void song() {
//我就想再唱歌之前先告诉大家一声我要唱歌了
System.out.println("我要开始唱歌了");
super.song();
}
public static void main(String[] args) {
B b = new B();
//现在我要高歌一首
b.song();
}
}
结果
我要开始唱歌了
我要开始说话了
这结果怎么是这样子,我明明就是想唱歌呀,为啥又说我开始说话了,
其实是因为类B继承了类A ,在类A中的song实现细节中用了say()方法,导致在类B中先走了song()方法,又走了say()方法,但是对于类B来说我不想知道你走了啥方法,我就是想要的功能而已,唱歌就是唱歌,说话就是说话。当然我们也可以将类A中的song()方法改成不调用say()方法,但是这个不应该和类A的实现细节有关系啊,我只是提供了我的API接口,功能不变的情况下,我的内部实现细节怎么变都应该是没有问题的啊,所以这就是继承暴漏了API的实现细节出现的问题。
而复合就不会有这样的问题了,把类A做为一个私有域放入类B中。再调用类A的song()方法
public class B {
private A a;
public B(A a) {
this.a = a;
}
public void say() {
System.out.println("我要开始说话了");
a.say();
}
public void song() {
System.out.println("我要开始唱歌了");
a.song();
}
public static void main(String[] args) {
B b = new B(new A());
//现在我要高歌一首
b.song();
}
}
结果
我要开始唱歌了
可以看出复合屏蔽了类的实现细节,专注于功能而不关注细节
所以继承要严格的遵循is-a的关系,否则不能瞎用继承,在用继承的时候想好继承是不是只是为了扩展功能。否则就不得不用复合来代替继承了。
例子来自于:https://blog.csdn.net/weixin_44130081/article/details/90375829