优先使用对象组合而不是类继承

文章内容参考自:

  1. http://www.hautelooktech.com/2013/02/05/design-principle-favor-composition-over-inheritance/
  2. 《agiledevelopment principles patterns and practices》

 

概述

继承和组合都能达到一个代码复用的效果,但是类的继承通常是白箱复用,对象组合通常为黑箱复用。我们在使用继承的时候同时也就拥有了父对象中的保护成员,增加了耦合度。而对象组合就只需要在使用的时候接口稳定,耦合度低。

Is a和has a

我们怎么来判断是用继承还是组合呢?只有在对象之间关系具有很强的is a关系的时候才使用继承。

那么继承为什么有的时候反而不好呢。下面举个简单的例子。

例子

假设我们需要设计音乐播放器。现在需要设计连个。一个是record player,另外一个是8轨道的播放器。所以现在我们先设计一个基类。

abstract class AbstractPlayer

{

public functionplay()

{

echo"I'm playing music through my speakers!";

}

public functionstop()

{

echo"I'm not playing music anymore.";

}

}

看起来好像可以了。然后我们设计两个类RecordPlayer和EightTrackPlayer,都继承自AbstractPlayer。

class RecordPlayer extends AbstractPlayer

{

}

class EightTrackPlayer extendsAbstractPlayer

{

}

现在,新的需求来了,我们需要完成一个随身听播放器。他和上面两个播放器很像,但是需要从耳机来播放音乐:

class PortableCassettePlayer extendsAbstractPlayer

{

public functionplay()

{

echo"I'm playing music through headphones!";

}

}

看起来没问题哈。我们的PortableCassettePlayer继承自AbstractPlayer,并且修改了一点Play的方法。更进一步,我们需要实现一个MPS播放器。

class Mp3Player extends AbstractPlayer

{

public function play()

{

echo "I'm playing music through headphones!";

}

}

我们发现我们实际上是直接复制了PortableCassettePlayer类的play函数。我怕我们不能直接使用基类的play函数因为MP3需要使用耳塞来播放音乐。我们可能想,我们可以继承PortableCassettePlayer啊,但是这样我们也就拥有了随身听播放器的其他功能,比如插入磁带,但是其实MP3是不需要的。我们发现我们本来想用继承来复用代码,但是实际上我们的代码确更加的重复了。

然后我们看使用组合怎么来做:

interface PlayBehaviorInterface

{

public function play();

}

class PlayBehaviorSpeakers implements PlayBehaviorInterface

{

public function play()

{

echo "I'm playing music through my speakers!";

}

}

class PlayBehaviorHeadphones implements PlayBehaviorInterface

{

public function play()

{

echo "I'm playing music through headphones!";

}

}

现在我们的播放更能被抽离出来了。

abstract class AbstractPlayer

{

protected$play_behavior;

abstract protectedfunction _createPlayBehavior();

public function__construct()

{

$this->setPlayBehavior($this->_createPlayBehavior());

}

public functionplay()

{

$this->play_behavior->play();

}

public functionsetPlayBehavior(PlayBehaviorInterface $play_behavior)

{

$this->play_behavior= $play_behavior;

}

public functionstop()

{

echo"I'm not playing music anymore.";

}

}

我们看到AbstractPlayer有一个创建Player的方法,也由一个setPlayBehavior的方法。这就允许我们在运行的时候动态的改变play的行为。我们看看Mp3播放器:

class Mp3Player extends AbstractPlayer

{

protected function_createPlayBehavior()

{

returnnew PlayBehaviorHeadphones;

}

}

试想以后,假设我们的M篇

播放器需要支持蓝牙播放,很方便的就可以修改了:

class PlayBehaviorBluetooth implementsPlayBehaviorInterface

{

public functionplay()

{

echo"I'm playing music wirelessly through Bluetooth!";

}

}

新的蓝牙功能可以再运行的时候动态的设置给播放器:

$mp3_player = new Mp3Player;

$mp3_player->play(); //echoes "I'mplaying music through headphones!"

$mp3_player->setPlayBehavior(newPlayBehaviorBluetooth);

$mp3_player->play(); //echoes "I'mplaying music wirelessly through Bluetooth!"

抽象这些变化的行为到另外一个接口类中让我们可以更好的扩展他的功能。回顾上面的代码,我们其实用到了策略模式,不违背OCP原则,等。

IS A

Is a是就行为而言的。比如经典的长方形和正方形而言:

Class Rectangle {

Privatedouble mWidth;

Privatedouble mHight;

Publicvoid setWidth(double width) {

mWidth= width;

}

publicvoid setHigh(tdouble hight) {

mHight= hight;

}

publicdouble area() {

returnmWidth * mHight;

}

}

class Square extends Rectangle {

Public void setWidth(double width) {

Super.setWidth(width);

Super.setHight(hight);

}

publicvoid setHigh(tdouble hight) {

Super.setWidth(width);

Super.setHight(hight);

}

}

void adjust(Rectangle r) {

r.setWidth(5);

r.setHeight(4);

asset(r.area()== 20);

}

从设计正方形类的人角度来看,正方形就是一个特殊的长方形。但是从编写者角度来看,他是基于假设width和heigh独立变化来判定结果的。所以导致断言错误。在这个地方Square就不能替换Rectangle了,因为Square在adjust中的行为方式和Rectangle的行为方式已经不一样了,他们违反了LSP原则。

我们可以添加if else来确定对象的类型啊,来判定是rectangle或者Square啊,那么以后新增子类都会在这个地方来修改代码,使代码的各个部分充斥着if else,违背OCP原则。

设计模式(笔记)优先使用对象组合而不是类继承相关推荐

  1. 面向对象设计原则——优先使用对象组合,而不是继承(组合以及与继承的区别)

    看到面向对象设计原则中的合成复用原则: 优先使用对象组合,而不是继承 类继承:也叫白箱复用 对象组合:也叫黑箱复用. 继承某种程度上破坏了封装性,子父类之间的耦合性过高. 对象组合只要求被组合的对象具 ...

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

    新的一周,新的干货分享 大家知道,面向对象有三个特征:继承.封装和多态.现在,我们谈谈关于继承的一些问题.了解一下继承的优点.缺点,以及继承缺点的解决方案. 继承的起源,来自于多个类中相同特征和行为的 ...

  3. 设计模式笔记九:组合模式

    原文:http://www.runoob.com/design-pattern/ 少许个人理解,如有错误请指出.欢迎一起讨论. 组合模式(Composite Pattern) 又叫部分整体模式,是用于 ...

  4. 设计模式笔记零:设计模式简介

    原文:http://www.runoob.com/design-pattern/design-pattern-intro.html 少许个人理解,如有错误请指出 设计模式简介 设计模式:设计模式是软件 ...

  5. 设计模式笔记(1)---开篇(文章索引)

    概念 设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案. 面向对象的设计模式描述了面向对象设计过程中,在特定场景下类与相互通讯的对象之间常见的组织关系. 设计模式与面向对象 面向对象设计模 ...

  6. 【笔记】设计模式 | 5种设计模式笔记整理

    跟着b站的设计模式教程学的,以下是目前学习了的5种设计模式的笔记整理 设计模式简介 软件设计的现状:由于客户需求等原因需要频繁的变更软件内部的代码.所以能否设计出复用性尽可能高的程序以解决软件设计的复 ...

  7. 结构型模式的设计模式笔记

    此笔记是在软件秘笈-设计模式那点事上做的笔记 一.适配器模式 1.设计思路 既有的软件结构具有稳定运行的基础,但是却无法直接利用到新的程序当中,这时就需要一个适配器,在原有内容和新的结果之间沟通,从而 ...

  8. 设计模式笔记七:桥接模式

    原文:http://www.runoob.com/design-pattern/ 少许个人理解,如有错误请指出.欢迎一起讨论. 同之前的原型模式,这一节菜鸟教程也比较抽象,仍然找一篇好懂的博客文章补充 ...

  9. boolan 设计模式笔记

    设计模式 什么是设计模式 每一个描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案.这样,你就能一次又一次地使用该方案而不必做重复劳动". --Christopher Alex ...

最新文章

  1. 四轴飞行器1.1 Matlab 姿态显示
  2. flink读写hive-代码方式
  3. java流与文件——操作文件
  4. python decimal 转 float_python教程之二python数学运算
  5. WINDOWS SERVER 2008 R2 GHO 纯净版
  6. linux initrd usb热插拔,8.3 发行注记 Red Hat Enterprise Linux 8 | Red Hat Customer Portal
  7. 51nod 1086 背包问题 V2 【二进制/多重背包】
  8. js点击a链接弹出alert对话框
  9. 手機短信阻擊中國化工項目
  10. word毕业论文导出高清pdf
  11. 【零基础学JS -2】 适合编写JS的编辑器
  12. 文件mime类型大全
  13. pytorch池化maxpool2D注意事项
  14. 【笔试题目整理】 网易2018校园招聘数据分析工程师笔试卷
  15. 安卓教室会议室预约系统源码
  16. 刚刚手贱把D盘设置为活动分区,导致无法进系统。来看看我的解决方法
  17. 关于克苏鲁神话的细节
  18. 观影感受 之 《绿皮书》
  19. Linux学习之——/etc/sysconfig目录
  20. 便携式明渠流量计,您了解有多少?

热门文章

  1. c语言程序中,整型常量的书写形式不包括_________.,??C语言程序中,整型常量的书写形式不包括_________。????...
  2. 模式识别学习笔记-lecture2-统计判别1
  3. 如何快速的推广自己网站(博客)呢?
  4. LINUX基础之 压缩归档篇(二)
  5. 加拿大存储厂商将在二战掩体中建设云数据中心
  6. qtabbar设置不同宽度_前端之css(宽高)设置小技巧
  7. Ubuntu制作本地软件源
  8. 学生为什么要学python_碎碎念|为什么要学Python
  9. ftp服务器文件能预览吗,ftp服务器 文件预览
  10. 单片机数字定时闹钟设计c语言,电子设计 基于51单片机的定时闹钟设计.doc