首页 > 其他分享 >为什么更推荐使用组合而非继承关系?

为什么更推荐使用组合而非继承关系?

时间:2022-12-24 22:12:23浏览次数:60  
标签:fly ... 组合 继承 推荐 eat void public

前言

最近在看公司项目的代码,看到了大量的继承体系,而且还是继承了多层,维护、阅读都十分的困难。在查阅了一些资料以后,包括《Effective Java》一书中的第16条提到“组合优先于继承”。那继承到底会暴露什么问题呢?为什么更推荐优先使用组合呢?

欢迎关注微信公众号「JAVA旭阳」交流和学习

继承带来的问题

老实讲,项目中为什么大量使用继承,估计初版设计的人是想实现代码的复用,但是的确带来不少的问题。

继承是面向对象重要特性之一,语义上是表达 is-a的关系,但是它会破坏封装性。我们举个例子:

假设我们要设计一个关于鸟的类。我们将“鸟类”这样一个抽象的事物概念,定义为一个抽象类 AbstractBird,默认有eat吃东西的行为。所有更细分的鸟,比如麻雀、鸽子、鸵鸟等,都继承这个抽象类。

public class AbstractBird { 
 	//... 省略其他属性和方法... 
 	public void eat() { //... }
}

// 鸵鸟
public class Ostrich extends AbstractBird { 

}

但是,这时候搞不清楚情况的人根据需求给AbstractBird添加一个fly()的行为。但是对于鸵鸟这个子类来说,并不会飞,你如果不做任何处理,相当于让鸵鸟有了飞翔的功能,不符合设计。聪明的你想到了,那就重写以下吧,抛出一个异常,如下所示:

public class AbstractBird { 
 	//... 省略其他属性和方法... 
 	public void eat() { //... }

    public void fly() { //... }
}

// 鸵鸟
public class Ostrich extends AbstractBird { 
 //... 省略其他属性和方法... 
 public void fly() { 
     throw new UnSupportedMethodException("I can't fly.'");  
 }  
}

这种设计思路虽然可以解决问题,但不够优美。因为除了鸵鸟之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,我们都需要重写 fly() 方法,抛出异常。而且真正好的设计,对于鸵鸟和企鹅来说,就不应该暴露给他们fly()这种不该暴露的接口,增加外部调用的负担。

这里只提到了fly(),如果还有下蛋egg()、唱歌sing()这么多行为,总不能都冗杂在父类里吧。关键像我们的项目同事,基本上把所有的类都写到了父类中,真的特别难以维护。

小结一下继承带来的问题:

  1. 子类继承了父类所有的行为,会让子类无意的暴露的不必要的接口,破坏封装性。
  2. 如果继承层级比较多,那么代码的复杂度、可阅读型就可想而知的难了。
  3. 另外一个点,就是非常不好做单元测试。

针对于这种问题,组合能怎么解决呢?

组合的好处

组合,顾名思意,就是把另外一个对象做成当前这个对象的一部分,是组成我的一部分,它也能很好的实现代码的复用,语义上表达的是has-a的意思,我有xxx的能力,我有xxx的功能。

那我们看看针对上面的例子,用组合的方式该如何实现呢?

  • 定义接口
public interface Eatable {
    void eat();
} 
public interface Flyable { 
    void fly(); 
} 

public class EatAbility implements Eatable { 
    @Override 
    public void eat() {
        System.out.println("I can eat");
    } 
}  // 省

public class FlyAbility implements Flyable { 
    @Override 
    public void fly() {
        System.out.println("I can fly");
    } 
}  // 省
  1. 组合鸵鸟类
public class Ostrich implements Eatable {// 鸵鸟
 	private Eatable eatable = new EatAbility(); // 组合
 	//... 省略其他属性和方法... 
	@Override 
    public void eat() { 
        eatable.eat(); // 委托
	} 
}

你看对于鸵鸟这个子类来说,只暴露了它有的能力,那就是eat,没有暴露fly的接口。

从理论上讲,通过组合、接口、委托三个技术手段,我们完全可以替换掉继承,在项目中不用或者少用继承关系,特别是一些复杂的继承关系。

继承真的无用武之地了?

既然面向对象中有继承这玩意,说明它并非一无是处的。

如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。反之,系统越不稳定,继承层次很深,继承关系复杂,我们就尽量使用组合来替代继承。

除此之外,还有一些设计模式会固定使用继承或者组合。比如,装饰者模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。

总结

不知道大家项目中继承用的多吗?其实在JDK中就有许多违反这条原则的地方,比如栈Stack类并不是Vector,不应该有继承关系,但是实际上就是继承自Vector。不管如何,在项目中决定使用继承而不是组合前,一定要考虑清楚,子类是否真的是父类的子类型?以后父类会不会经常变动的可能?父类的某些API是否存在缺陷,如果有的话也会随着子类扩散出去。

欢迎关注微信公众号「JAVA旭阳」交流和学习
更多学习资料请移步:程序员成神之路

标签:fly,...,组合,继承,推荐,eat,void,public
From: https://www.cnblogs.com/alvinscript/p/17003457.html

相关文章

  • Python学习笔记--从继承开始继续
    继承的基础语法单继承:多继承:一个子类继承多个父类pass关键字补全语法注意事项:复写和使用父类成员复写父类成员也就是相当于Java中的方法重写调用父类成员......
  • linux 中 {} 组合%实现截取文件名
     001、[root@pc1test]#ls[root@pc1test]#var=mnopqrst##测试变量[root@pc1test]#echo$var##打印出该变量mnopqrst[root@pc1test]#echo${......
  • 省选08. 组合计数
    P4091[HEOI2016/TJOI2016]求和有一个重要的通项公式\[\begin{Bmatrix}n\\m\end{Bmatrix}=\sum_{i=0}^{m}\frac{i^n(-1)^{m-i}}{i!(m-i)!}\]\[ans=\sum_{i=0}^{n}\sum......
  • 按下 Ctrl+Alt+L 组合键
    在用idea进行编程时,此按键作为格式化代码的常用按键,却与QQ的安全锁的按键相同,按下可能会导致无法格式化代码,而使QQ安全锁响应解决方法:1.更改idea的快捷键file->settings......
  • Kagol:2022年最值得推荐的前端开源文章
    大家好,我是Kagol,VueDevUI作者,从2020年开始一直专注于前端开源组件库的建设,在前端开源组件库、开源社区运营方面积累了一些经验,2020年主要的创作也是围绕前端组件库和开......
  • 77. 组合
    【题目描述】给定两个整数 ​​n​​​ 和 ​​k​​​,返回范围 ​​[1,n]​​​ 中所有可能的 ​​k​​ 个数的组合。你可以按 任何顺序 返回答案。【示例】【......
  • Java继承
    显示所有属性:alt+shift+s封装快捷键:alt+shift+s+r什么是继承?继承是符合人类现实世界的一种概念,它的作用把相同的属性和方法抽取出来,提供可以被继承的子类使用,实现......
  • Codeforces 1630 E Expected Components 题解 (组合数学)
    题目链接首先明确概念:排列。指的就是一个把数组a重排得到的序列,两个排列相等当且仅当它们对应位全都相等环形排列。指的是把数组a重排得到的序列首尾相接得到的环形数......
  • 火山引擎 DataTester 上线“流程画布”功能,支持组合型 A/B 实验分析
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群在精细化运营的时代,运营活动同样需要有精细化的策略,例如在年末大促活动中,设计APP......
  • CSS-属性选择器(推荐常用)-2022-12-23
    <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title><style>.demoa{float:left;display:block;heigh......