博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
设计模式:灵活编程(装饰模式)
阅读量:6369 次
发布时间:2019-06-23

本文共 2723 字,大约阅读时间需要 9 分钟。

装饰模式,英文叫Decorator Pattern,又叫装饰者模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

在设计模式中,一直推荐多用组合,少用继承的结构。使用组合来处理对象的行为,可以在运行时动态的进行扩展。而继承是共享父类特性的一种简单的方法,但可能会使你将需要改变的特性硬编码到继承体系中,而这常会降低系统灵活性。

问题

将所有功能都寄托于继承体系上会导致类的增多,代码的冗余!

首先我定义一个饮品 Drink 及其子类 Cappuccino :

abstract class Drink{    abstract function getPrice();}class Cappuccino extends Drink {    private $_price = 50;        public function getPrice()    {        return $this->_price;    }}

 getPrice 方法用于返回当前饮品的价格,对于单一价格来说,这种结构还不错。但是,如果我希望 Cappuccino 加糖和加牛奶都会加收金额,并且获取到加价后的价格怎么处理?有一个办法是从 Cappuccino 对象派生:

class CappuccinoAddSugar extends Cappuccino{    public function getPrice()    {        return parent::getPrice() + 2;    }}class CappuccinoAddMilk extends Cappuccino{    public function getPrice()    {        return parent::getPrice() + 4;    }}

这样就可以很轻松获得一个加糖的卡布奇诺价格:

$price = new CappuccinoAddSugar();echo $price->getPrice(); // 52

 

看到这也能看到这种结构的弊端,如果我们需要既加糖又加奶的价格呢?OK,可以再加一个派生类。但是,如果需要加蜂蜜的呢?既加蜂蜜又加牛奶的呢?...... 随着功能增多你会发现类成“爆炸式”的增长,而且代码结构都是重复的。

实现

装饰模式使用组合和委托而不是只使用继承来解决功能变化的问题。装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。通过使用不同的装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。

我重写下上面的示例:

abstract class Drink{    abstract function getPrice();}class Cappuccino extends Drink{    private $_price = 50;    public function getPrice()    {        return $this->_price;    }}abstract class DrinkDecorator extends Drink{    protected $drink;    public function __construct(Drink $drink)    {        $this->drink = $drink;    }}

这里我引入了一个新类 DrinkDecorator ,定义了以一个 Drink 对象为参数的构造方法,传入的对象被保存在 $drink 属性中,该属性被声明为 protected ,以便子类可以访问它。下面我重新定义 CappuccinoAddSugar 和 CappuccinoAddMilk 类。

class CappuccinoAddSugarDecorator extends DrinkDecorator{    public function getPrice()    {        return $this->drink->getPrice() + 2;    }}class CappuccinoAddMilkDecorator extends DrinkDecorator{    public function getPrice()    {        return $this->drink->getPrice() + 4;    }}

这些类都扩展自 DrinkDecorator 类,这意味着它们拥有指向 Drink 对象的引用。当 getPrice 方法被调用时,这些类都会先调用所引用的 Drink 对象的 getPrice 方法,然后执行自己特有的操作。

通过像这样使用组合和委托,可以在运行时轻松地合并对象。

// 基础价格$price = new Cappuccino();echo $price->getPrice(); // 50// 加糖价格$price = new CappuccinoAddSugarDecorator(new Cappuccino());echo $price->getPrice(); // 52// 加奶价格$price = new CappuccinoAddMilkDecorator(new Cappuccino());echo $price->getPrice(); // 54// 既加糖又加奶$price = new CappuccinoAddMilkDecorator(new CappuccinoAddSugarDecorator(new Cappuccino()));echo $price->getPrice(); // 56

这样的模型极具扩展性。可以非常轻松地添加新的装饰器或者新的组件。通过使用大量的装饰器,可以在运行时创建极为灵活的结构。

总结

组合和继承通常都是同时使用的。装饰模式提供了更多的灵活性,但也提高了代码的复杂性。因为装饰对象作为子对象的包装,所以保持基类中的方法尽可能少是很重要的。如果一个基类具有大量特性,那么装饰对象不得不为它们包装的对象的所有 public 方法加上委托。你可以用一个抽象的装饰类来实现,不过这仍旧会带来耦合,并可能导致 bug 出现。感谢阅读,再会!

转载于:https://www.cnblogs.com/bndong/p/9198611.html

你可能感兴趣的文章
地图点聚合优化方案
查看>>
Google Chrome 快捷方式
查看>>
备考PMP心得体会
查看>>
vue proxy匹配规则
查看>>
线上应用故障排查之一:高CPU占用
查看>>
Extend Volume 操作 - 每天5分钟玩转 OpenStack(56)
查看>>
IronPython教程
查看>>
squid via检测转发循环
查看>>
计算分页
查看>>
iptables 做nat路由器脚本
查看>>
数据结构(C语言版)第三章:栈和队列
查看>>
Stopping and/or Restarting an embedded Jetty in...
查看>>
Oracle存储过程中的数据集输入参数
查看>>
vsftp 配置
查看>>
VCSA中配置时间和时区,实测至6.5适用
查看>>
高并发IM系统架构优化实践
查看>>
产品经理教你玩转阿里云负载均衡SLB系列(一):快速入门--什么是负载均衡
查看>>
有关linux--进程组、会话、守护进程详解
查看>>
我的友情链接
查看>>
monkeyrunner运行Python脚本来检查apk渠道和验证是否可以调用微信
查看>>