面向对象之组合VS继承:继承过时了?
在阅读Effective Java中的第16条时发现了一个有趣的机制或者说是模式,那就是组合(文中翻译为复用,但是作者认为组合更能体现这种模式的精神),并且文中建议使用组合。
那什么是组合,组合相较于继承的优点在哪里,缺点又有哪些,组合和继承更适合怎样的场景,更重要的是作者在校基础课程的学习中尽然都没有接触到组合这个概念,实在有理由探索一下!
我们分别了解一下组合和继承的概念
组合
- 组合就是 A类的对象是B类的成员变量。相当于 A类对象是B类对象的一个变量,A类中的所有功能,B类都可以通过A类对象的调用来实现。
- 组合体现的是整体与部分、拥有的关系,即 has - a 的关系
让我们用一段代码来演示一下
class 电池{
public void 放电(){
System.out.println("输出电能");
}
}
class 屏幕{
public void 亮屏{
System.out.println("显示内容");
}
}
class CPU{
public void 运算{
System.out.println("浮点计算");
}
}
class 手机{
private 电池 dc;
private 屏幕 pm;
private CPU cpu;
手机(电池 dc,屏幕 pm,CPU cpu){
this.dc = dc;
this.pm = pm;
this.cpu = cpu;
}
public 开机(){
dc.放电();
cpu.运算();
pm.亮屏();
System.out.println("开机成功");
}
public static void main(String[] args) {
电池 dc = new 电池();
屏幕 pm = new 屏幕();
CPU cpu = new CPU();
手机 sj = new 手机(dc,pm,cpu);
sj.开机();
}
}
继承
- 一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力
- 继承体现的是父与子,血缘的关系,即 is - a 的关系
让我们继续放代码来演示
class Animal{
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("呼吸中...");
}
}
class Bird extends Animal{
//创建子类独有的方法fly()
public void fly(){
System.out.println("我是鸟,我在天空中自由的飞翔...");
}
}
class Wolf extends Animal{
//创建子类独有的方法run()
public void run(){
System.out.println("我是狼,我在草原上快速奔跑...");
}
}
public class InheritTest{
public static void main(String[] args){
Bird b=new Bird();
b.breath();
b.fly();
Wolf w=new Wolf();
w.beat();
w.run();
---------- 运行Java程序 ----------
呼吸中...
我是鸟,我在天空中自由的飞翔...
心脏跳动...
我是狼,我在草原上快速奔跑...
}
}
区别和联系
区别
- 组合是一种 has-a 的 关系
- 继承是一种 is-a 的 关系
- 继承结构中,父类的内部细节对于子类是可见的。所以通过继承的代码复用是一种白盒式代码复用。
- 如果父类的实现跟随版本而发生改变,那么子类的实现也将随之改变。这样就导致了子类行为的不可预知性;
- 组合是通过对现有的类进行拼装(组合)产生新的、更复杂的功能。因为在类之间,各自的内部细节是不可见的,所以这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
- 继承,在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)
- 组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
联系
- 组合和继承都是代码复用的一种方式
优点和缺点
在 Effective Java 中非常推荐在需要代码重用的情况下优先使用组合而不是大家常见的继承,让我们们他们各有什么优缺点
- 优点
| 组合 | 继承 |
|---|---|
| 不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 子类能自动继承父类的接口 |
| 具有较好的可扩展性 | 创建子类的对象时,无须创建父类的对象 |
| 支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | |
| 整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 |
- 缺点
| 组合 | 继承 |
|---|---|
| 整体类不能自动获得和局部类同样的接口 | 子类不能改变父类的接口 |
| 创建整体类的对象时,需要创建所有局部类的对象 | 不支持动态继承。在运行时,子类无法选择不同的父类 |
| 空 | 支持扩展,但是往往以增加系统结构的复杂度为代价 |
| 空 | 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
可以发现组合相对于继承是一种更好的代码重用手段,但是继承就没有用武之地么,让我们接下来看他们各自的使用场合和情况
不同的场景
- 我们通过最初的组合和继承的示例代码再结合 概念中的 has - a 和 is -a 的描述,能够体会到组合和继承是为了两种场景的重用而设计的
- 当B类真正是A类的 抽象类型时,才适用继承,例如 人 是 婴儿的 抽象类型 是适用于继承的 ,而当 猴子 为 了 重用 人 的呼吸,进食,行动 而 继承于 人 这就是 不适用的.
- 而除去第一种场景,其他场景使用组合是更合适的
- 并且在第一种场景中,使用组合也不是不可以,但继承是最好的方案
结语
文章至此处,想必大家已经明白了组合和继承的关系,并且已经知道在何种情况下使用他们,那么组合VS继承 的 结果就是在更多的场景中使用组合是更好的方案。
面向对象之组合VS继承:继承过时了?的更多相关文章
- Python面向对象(组合、菱形继承、多态)
今日内容: 1.组合 2.菱形继承 3.多态与多态性 昨天内容重点回顾: 1)调用也叫实例化:发生了2件事 1.创造空对象 2.触发对象下的__init__方法,然后将p连同参数一同传给init ...
- Python面向对象的三大特性之继承和组合
继承和组合 一.组合 组合:组合指的是,在一个类中以另外一个类的对象(也就是实例)作为数据属性,称为类的组合 也就是说:一个类的属性是另一个类的对象,就是组合 例子: 圆环是由两个圆组成的,圆环的面积 ...
- javascript之面向对象程序设计(对象和继承)
总结的文章略长,慎点. 知识点预热 引用类型:引用类型的值(对象)是引用类型的一个实例.在ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起.在其他面向对象语言中被称为类,虽然 ...
- JS--我发现,原来你是这样的JS:面向对象编程OOP[3]--(JS继承)
一.面向对象编程(继承) 这篇博客是面向对象编程的第三篇,JS继承.继承顾名思义,就是获取父辈的各种"财产"(属性和方法). 怎么实现继承? 我们的JavaScript比较特别了, ...
- python基础学习Day17 面向对象的三大特性之继承、类与对象名称空间小试
一.课前回顾 类:具有相同属性和方法的一类事物 实例化:类名() 过程: 开辟了一块内存空间 执行init方法 封装属性 自动的把self返回给实例化对象的地方 对象:实例 一个实实在在存在的实体 组 ...
- 1.Java基础-面向对象编程思想(封装继承多态接口)
封装: 1.定义:隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别. 2.封装的目的是:增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的 ...
- Javascript面向对象特性实现封装、继承、接口详细案例——进级高手篇
Javascript面向对象特性实现(封装.继承.接口) Javascript作为弱类型语言,和Java.php等服务端脚本语言相比,拥有极强的灵活性.对于小型的web需求,在编写javascript ...
- JavaScript 面向对象程序设计(下)——继承与多态 【转】
JavaScript 面向对象程序设计(下)--继承与多态 前面我们讨论了如何在 JavaScript 语言中实现对私有实例成员.公有实例成员.私有静态成员.公有静态成员和静态类的封装.这次我们来讨论 ...
- (二)Javascript面向对象编程:构造函数的继承
Javascript面向对象编程:构造函数的继承 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承 ...
随机推荐
- JS中的事件委托(事件代理)
一步一步来说说事件委托(或者有的资料叫事件代理) js中事件冒泡我们知道,子元素身上的事件会冒泡到父元素身上. 事件代理就是,本来加在子元素身上的事件,加在了其父级身上. 那就产生了问题:父级那么多子 ...
- POJ 2411 Mondriaan's Dream -- 状压DP
题目:Mondriaan's Dream 链接:http://poj.org/problem?id=2411 题意:用 1*2 的瓷砖去填 n*m 的地板,问有多少种填法. 思路: 很久很久以前便做过 ...
- PHP 高级工程面试题汇总
PHP高级工程面试题汇总(2018.05) 1.给你四个坐标点,判断它们能不能组成一个矩形,如判断([0,0],[0,1],[1,1],[1,0])能组成一个矩形. 勾股定理,矩形是对角线相等的四边形 ...
- git在多迭代版本的应用
名词解释: 1.迭代: 就是对于项目功能的一个分类.如项目需要新增一个地图功能,则地图功能是一个迭代. 2.gitlab机器人 操作: 1.如果将要进行一个新功能的开发,从稳定分支上拉取创建一个新的分 ...
- poj-1904(强连通缩点)
题意:有n个王子,每个王子都有k个喜欢的女生,王子挑选喜欢的女生匹配,然后再给你n个王子最开始就定好的匹配,每个王子输出能够结合且不影响其他王子的女生匹配 解题思路:强连通缩点,每个王子与其喜欢的女生 ...
- Kaggle-房价预测
完整代码见kaggle kernel 或 Github 比赛页面:https://www.kaggle.com/c/house-prices-advanced-regression-technique ...
- P1462 通往奥格瑞玛的道路
P1462 通往奥格瑞玛的道路 题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡 ...
- 【dp】导弹拦截
题目链接 https://www.luogu.org/problemnew/show/P1020 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的 ...
- Linux安装gitbook
安装g++ 默认centos7上是没有安装g++ 通过命令: $ yum -y install gcc openssl-devel gcc-c++ compat-gcc-34 compat-gcc-3 ...
- 简单的makefile
单一程序 准备一个hello.c #include <stdio.h> int main(void) { printf("Hello World!\n"); } gcc ...