面向对象之组合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面向对象编程:构造函数的继承 这个系列的第一部分,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例. 今天要介绍的是,对象之间的"继承 ...
随机推荐
- Map的clear与new Map的区别
对于clear与new Map的区别.我们首先来看一个例子,本例子是我在实际开发中遇到的,需求就是讲map放入到list中,说白了就是list转map,有两种实现方式,分别是: // 方案一 Map& ...
- 【微信小程序】rpx尺寸单位的应用
前言:微信小程序中的rpx尺寸单位用起来很方便.他是怎么实现计算的呢?(这里要注意的是,常规浏览器解析css代码的时候会把font-size小于12px的字体转成12px,不会让他小于12px的,而微 ...
- parcel+vue入门
一.parcel简单使用 npm install -D parcel-bundler npm init -y (-y表示yes,跳过项目初始化提问阶段,直接生成package.json 文件.) Pa ...
- Fedora 24系统基本命令
Fedora 24基本命令 一. DNF软件管理 1. 修改配置:在/etc/dnf/dnf.conf中加入fastestmirror=true.keepcache=true ...
- 常规 nginx 代理apache
nginx: server { server_name www.xx.com xx.com; access_log /data/wwwlogs/www.xx.com_nginx.log combine ...
- jmeter笔记(7)--参数化--用户定义的变量
录制的脚本里面有很多的相同的数据的时候,比如服务器ip,端口号等,当更换服务器的时候,就需要手动的修改脚本里面对应的服务器ip和端口号,比较繁琐,jmeter里面有一个用户自定义变量能很好的解决这个问 ...
- yii2 使用指定数据库执行createCommand
Yii::$app->dbName->createCommand($sql)->queryAll(); 指定dbName数据库配置
- 20175209 《Arrays和String单元测试》
20175209 <Arrays和String单元测试> 题目 在IDEA中以TDD的方式对String类和Arrays类进行学习,测试相关方法的正常,错误和边界情况 String类 ch ...
- css 修改placeholder的颜色
input::-webkit-input-placeholder { color: #ff0000; } input::-moz-input-placeholder { color: #ff0000; ...
- CSS white-space属性详解
概述 CSS的white-space属性用于指定如何处理容器中的空白字符,例如:空格( ).换行(\n).缩进(\t)等. white-space出自CSS1,适用于块状元素,具有继承性,支持IE 5 ...