java 组合优与继承_Java中为什么老鸟要告诉你优先使用组合而不是继承?

2418e047ff5d5439727cc3f5c770b36a.png

新的一周,新的干货分享

483732c50c3c13ca275877b7e6976b70.png

0ffc557ad075c6a81625eb37e98ac803.png

大家知道,面向对象有三个特征:继承、封装和多态。现在,我们谈谈关于继承的一些问题。了解一下继承的优点、缺点,以及继承缺点的解决方案。

继承的起源,来自于多个类中相同特征和行为的抽象。子类可以通过继承父类,那么可以调用父类中定义的方法和属性,从而达到代码重用的目的。另外,子类除了重用父类的代码以外,还可以扩展自身的属性和方法,来描述子类特有的特征和行为。

例如:人类和马类这两个类,有什么共同的特征和行为呢?都有年龄,都要呼吸,都可以行动,都要吃奶。我们可以把人类和马类相同的特征和行为抽取出来,形成一个父类:哺乳动物。人类和马类只要继承哺乳动物这个父类。那么,就可以直接重用哺乳动物父类中定义的属性和行为,相同的属性和行为就可以不用再重复描述。所以,通过继承,可以达到代码很大的重用。

除此之外,人类和马类还可以再定义自己本身的特征和行为。比如,马要吃草,人可以说话。那么,人类和马类可以在继承了哺乳动物父类的同时,再定义说话或吃草的行为,来扩展自身的特征和行为。

08aa144c3ad2c4d840a210d0ab32af3c.png

670c351c6ac7612a23c6a7bae8084f36.png

使用继承有很多优点,父类的大部分功能可以通过继承关系自动进入子类;修改或扩展继承而来的属性和方法较为容易。那么,如何判断两个类之间是否有继承关系呢?很简单,用”是”来判断。比如:马是动物。那么马继承于动物。玫瑰是植物。那么玫瑰继承于植物。骂人的时候,我们会说:你是畜牲,那么这个人继承于畜牲,象畜牲一样没有人性。你是猪,那么这个人继承于猪,象猪一样愚笨。这就是所谓的”is-a”。

但是,继承同样有很多缺点。人类在大千世界中,不断的认识世界,也不断的改造世界。人类曾经梦想,在天空中飞翔。那么,人类如何飞行呢?鸟可以飞行,那是因为鸟有翅膀这个特征,才会拥有飞这个行为。如果人类继承于鸟类,象鸟一样长出翅膀,那么也就可以有飞行的行为了。

f51590abe2907991ee903b456de3643f.png

f9cbf5cab47489e4be476271d7103baa.png

《封神榜》中的雷震子和天使,其实就是人类想在天空中飞行的一种想法。那就是继承鸟这个类,拥有鸟类的翅膀和飞行的行为。

那么,人类想下海应该怎么办呢?于是人类又在想,只要将人类继承于鱼类,拥有鱼的尾巴,那么就可以下海了。随着这种想法的产生,美人鱼就出现了。

aab52007b68f58304c779bd08740a669.png

那么,如果人类既想飞行,又想下海应该怎么办呢?有人说,那还不简单,把人类同时继承于鸟类和鱼类不就行了吗。但是,不好意思,在java中,类只能单根继承。也就是说,一个类只能有一个父类,不能同时继承两个父类。所以,从这里就可以看出继承的其中一个缺点:无法通过继承的方式,重用多个类中的代码。

除此之外,继承还有第二个缺点,那就是,父类的属性和方法,子类是无条件继承的。也就是说,不管子类愿意不愿意,都必须继承父类所有的属性和方法。比如,每个人都有自己父母,如果父母比较富有,那么子女就吃得好穿得好。如果父母是穷人,那么子女吃不饱穿不暖。很显然,所有的人都希望自己是富二代,官二代,而不想成为穷二代。不过,人是无法选择自己父母的。所以,父母的一切,自己是无条件接受的。

同样道理,如果人类继承于鸟类,我们希望拥有的是:鸟的翅膀和飞的行为。但是,鸟还有吃虫的行为,鸟还有下蛋的行为。这些是我们不希望拥有的。不过,如果人类继承于鸟类的话,那么吃虫和下蛋的行为,人类就得无条件接受了。所以,使用继承很容易造成方法的污染。一旦父类的属性和方法,在子类中不能完全适用。那么,也就不应该使用继承关系了。

cdb15e54d037a1e355dd39fad6b66c28.png

还有,从父类继承而来的实现是静态的,不能在运行时发生改变,不够灵活。比如,有一个人从春熙路到天府广场去。

03d2b64815382bacda03ce35330f218e.png

当我们调用run()方法时,就只能打印从父类中继承的run方法。如果这个人想骑单车去天府广场应该怎么办呢?有人说,简单,在CDMan中重写run()不就行了吗?

500e3690f259181dbf900b89177cc2ff.png

但是,如果这个人想骑单车走一段,再开汽车走一段,就没法通过继承和重写来实现了。所以,无论是从父类中继承的方法,还是子类重写的父类方法,实现的都是一种静态的复用。不能在运行时发生改变,灵活性比较差。

那么,如何解决继承的这些缺点呢?荀子在《劝学》中,有这样的一段话:”假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。”就是说,人可以骑马,即使这个人跑得不快,也可以到达千里之外。人可以坐船,即使这个人不会游泳,也可以到达江河的任何位置。君子其实没什么太多特别的地方,只不过善于利用工具而已。这就是所谓的”has-a”。拥有什么,或者使用什么。

荀子的这段话,指出了解决继承缺陷办法,那就是使用聚合/组合达到代码的复用。比如,人想上天怎么办呢?可以利用飞机上天。人想下海怎么办呢,可以利用轮船下海。并不要求人要长出翅膀,人要长出鱼尾。

f8a47e32da08b2c99444b67efb3d72a0.png

40501f9ff1de9aebab3d07e43a72f872.png

这样的解决方案,其实就是告诉我们,与其我们”是什么”,倒不如我们”用什么”。也就是用聚合/组合复用,去代替继承复用。把一些特征和行为抽取出来,形成工具类。然后通过聚合/组合成为当前类的属性。再调用其中的属性和行为达到代码重用的目的。

换句话说,用”has-a”(有什么或用什么)去替代”is-a”(是什么)。

e71e8752bd21f1d437f205d60eddf166.png

从以上代码可以看出,通过聚合/组合关系,可以解决继承的缺点。由于一个类可以建多个属性,也就是可以聚合多个类。所以,可以通过聚合/组合关系,重用多个类中的代码。

另外,我们可以选择一个类中是否应该具有某种行为,从而决定应该聚合那些类,不应该聚合那些类。这样,通过聚合/组合关系,也可以避免继承所带的方法污染问题。所以,使用聚合/组合,具有很强的代码重用性和灵活性。

聚合/组合复用也可以在运行时动态进行。新对象可以使用聚合/组合关系,将新的责任委派到合适的对象。

9207d38ef81153e5f6e0114ac540f2f8.png

从这里可以看出,人类可以随时改换交通工具,达到行走的目的。这种方式可以在运行期间,随时改变接口属性的实现类。从而调用不同实现类描述的具体方法,灵活性很强。

总结:

继承和聚合/组合都可以达到代码重用的目的。继承有自身的优点,父类的大部分功能可以通过继承关系自动进入子类;修改或扩展继承而来的实现较为容易。

但是,继承同样有缺点,

无法通过继承达到多个类代码的重用。父类的方法子类无条件继承,很容易造成方法污染。从父类中继承的方法,是一种静态的复用。不能在运行时发生改变,不够灵活。继承可以用,但使用继承需要谨慎。一般来说,使用继承有两个条件:

父类中所有的属性和方法,在子类中都适用。子类不需要再去重用别的类中的代码。如果不能满足这两个条件,那么就应该使用聚合/组合关系去替代继承,来达到代码的复用。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值