【深入PHP 面向对象】读书笔记(九) - 让面向对象编程更加灵活的模式(二) - 装饰模式
2017-10-02 20:51
579 查看
10.2 装饰模式
组合模式帮助我们聚合组件,而装饰模式则使用类似结构来帮助我们改变具体组件的功能。改模式同样体现了组合的重要性,但组合是在代码运行时实现的。10.2.1 问题
定义一个 Tile(区域)类及其子类:/* 区域类 表示部队单元所在的一个区域 */ abstract class Tile { /* 定义一个getWealthFactor()方法,用于计算在某个特定区域上所能获得的收益 */ abstract function getWealthFactor(); } /* Plain(平原)类 */ class Plains extends Tile { /* 该类的财富系数为2 */ private $wealthFactor = 2; function getWealthFactor() { return $this->wealthFactor; } }
Tile 类表示部队单元所在的一个区域。每个 Tile 对象都有明确的特征。我们定义一个 getWealthFactor() 方法,用于计算某个特定的区域被一个玩家所占有后的收益。例如,Plain(平原)对象的财富系数为2。
同时,我们还需要用于处理一些自然资源和人类滥用的效果,对地表钻石的分布和污染造成的破坏建模。有一个方法是从 Plains 对象派生:
/* DiamonPlains 钻石地表平原 */ class DiamondPlains extends Plains { function getWealthFactor() { /* 财富值为普通平原+2 */ return parent::getWealthFactor()+2; } } /* PollutedPlains 污染地表平原 */ class PollutedPlains extends Plains { function getWealthFactor() { /* 财富值为普通平原-4 */ return parent::getWealthFactor()-4; } }
关系类图如下:
现在,我们能非常轻松地获取一个特定地区的财富系数:
$diamond_tile = new DiamondPlains(); echo $diamond_tile->getWealthFactor(); $polluted_tile = new PollutedPlains(); echo $polluted_tile->getWealthFactor();
这样的结构显然不具有灵活性。我们可以获得有 diamond 的 plain 对象,也可以获取受污染的 plain 对象,但是我们很难同时获得既有钻石又受污染的 plain 对象,除非我们自找麻烦地创建一个 PollutedDiamondPlains 类。
因此,我们可以得出一个结论,功能定义完全依赖于继承体系会导致类的数量过多,而且代码会产生重复。
10.2.2 实现
装饰模式使用组合和委托而不是只使用继承来解b90c
决功能变化的问题。
我们重写这个游戏:
/* 区域类 表示部队单元所在的一个区域 */ abstract class Tile { /* 定义一个getWealthFactor()方法,用于计算在某个特定区域上所能获得的收益 */ abstract function getWealthFactor(); } /* Plain(平原)类 */ class Plains extends Tile { /* 该类的财富系数为2 */ private $wealthFactor = 2; function getWealthFactor() { return $this->wealthFactor; } } /* 引入一个新类TileDecorator,该类不实现getWealthFactor()方法,声明为抽象类 */ abstract class TileDecorator extends Tile { /* 定义一个Tile对象为参数的构造方法,并将该对象存入$tile属性中 */ protected $tile; function __construct(Tile $tile) { $this->tile = $tile; } }
下面再重新定义 Polluted 和 Diamond 的装饰类:
class DiamondDecorator extends TileDecorator { function getWealthFactor() { return $this->tile->getWealthFactor()+2; } } class PollutedDecorator extends TileDecorator { function getWealthFactor() { return $this->tile->getWealthFactor()-2; } }
这两个类(DiamondDecorator、PollutedDecorator)都扩展自 TileDecorator 类,都拥有指向 Tile 对象的引用。当这两个类调用 getWealthFactor() 函数时,会先调用 Tile 对象的 getWealthFactor() 方法,然后再执行自己特有的操作。
通过类图:
通过这样的组合和委托,可以在运行时轻松地合并对象。因为模式中所有的对象都扩展自 Tile,所有客户端代码并不需要知道内部是如何合并的。getWealthFactor() 方法在任何 Tile 对象中都是可用的,无论改对象是一个装饰对象还是一个真正的 Tile 对象。
普通的调用,无论是基本的平原(Plain),还是铺满钻石的平原(DiamondDecorator Plain):
$tile = new Plains(); echo $tile->getWealthFactor(); // 2 $tile = new DiamondDecorator(new Plains()); echo $tile->getWealthFactor(); // 4
而对于受污染的铺满钻石的平原(PollutedDecorator DiamondDecorator Plain),只需要再叠加一层就可以:
$tile = new PollutedDecorator(new DiamondDecorator(new Plains());); echo $tile->getWealthFactor(); // 0
这样的模型极具扩展性。我们可以非常轻松地添加新的装饰器或者新的组件。通过使用大量的装饰器,我们可以在运行时创建极为灵活的结构。
例子中的组件类 Plains 可以很方便地被改变,而不需要改动原来的类。即不需要创建 PollutedDiamondPlains 对象,就可以创建一个拥有钻石并被污染的 Plains 对象。
10.2.3 效果
装饰对象作为子对象的包装,所以保持基类中的方法尽可能少是十分重要的。如果一个基类具有大量特性,那么装饰对象不得不为它们包装的对象的所有 public 方法加上委托。也可以使用一个抽象的装饰类来实现,不过这可能会带来耦合,并可能导致 bug 的出现。相关文章推荐
- 【深入PHP 面向对象】读书笔记(八) - 让面向对象编程更加灵活的模式(一) - 组合模式
- 【深入PHP 面向对象】读书笔记(十) - 让面向对象编程更加灵活的模式(三) - 外观模式
- 深入PHP面向对象、模式与实践——让面向对象编程更加灵活的模式(3)
- 【深入PHP 面向对象】读书笔记(十六) - 企业模式(一) - 架构概述
- 六. PHP模式设计----让面向对象编程更加灵活的模式
- 【深入PHP 面向对象】读书笔记(十五) - 执行及描述任务(五) - 命令模式
- 【深入PHP 面向对象】读书笔记(二十一) - 企业模式(六) - 模板视图和视图助手
- 深入PHP:面向对象、模式与实践-读书笔记:对象工具1
- 【深入PHP 面向对象】读书笔记(二十) - 企业模式(五) - 页面控制器
- 【深入PHP 面向对象】读书笔记(五) - 模式
- 【深入PHP 面向对象】读书笔记(二十二) - 企业模式(七) - 业务逻辑层与事务脚本
- 【深入PHP 面向对象】读书笔记(十七) - 企业模式(二) - 注册表
- 【读书笔记-重构与模式】 组合模式-让面向对象更加灵活
- 【深入PHP 面向对象】读书笔记(十三) - 执行及描述任务(三) - 观察者模式
- 【深入PHP 面向对象】读书笔记(十四) - 执行及描述任务(四) - 访问者模式
- 【深入PHP 面向对象】读书笔记(十九) - 企业模式(四) - 应用控制器
- 【深入PHP 面向对象】读书笔记(十八) - 企业模式(三) - 表现层
- 【深入PHP 面向对象】读书笔记(六) - 模式原则
- 深入PHP面向对象、模式与实践——让面向对象编程更加灵活的模式(2)
- [李景山php] 深入理解PHP内核[读书笔记]--第五章:类和面向对象 --概览