设计模式:装饰器模式介绍和应用

简短描述

当前有一个功能完善的对象,如果我们想要给这个对象添加一个新的职责,那么我们可以用一个新的类去装饰它来实现对原有对象职责的扩展。新的类称为“装饰者”,原有的对象称为“被装饰者”。这种模式被称为装饰器模式。

现实生活中的装饰器模式

装饰器模式在现实生活中的例子简直太多了。比如我开了一个奶茶店,卖的是普通的奶茶。现在我想引入一个叫珍珠奶茶的商品,我要怎么做呢?

我是不是需要升级一下我的制作奶茶的机器,让它支持珍珠奶茶的做法?但这种成本估计比较高,说不定还没原来做奶茶的机器好用呢!实际上我只需要买一个能做“珍珠”的机器就行!把“珍珠”放进奶茶不就成了珍珠奶茶。原有的做奶茶的机器依然可以稳定地制作做奶茶,也避免花成本去学习操作新的机器。

在这个案例中,珍珠就是装饰者,奶茶就是被装饰者。

很容易就写出一个上面所描述的装饰器模式的代码。 首先定义一个饮料接口

interface Drink
{
    public function getPrice();
}

我们现在已经在卖奶茶了,下面是一个奶茶的类。价格已经订好了,10元!

class MilkTea implements Drink
{
    public function getPrice()
    {
        return 10;
    }
}

接着有一个新的品种,叫珍珠奶茶,我只要写一个珍珠的类,然后装饰一下奶茶,这个奶茶就成了珍珠奶茶。

class Boba implements Drink
{
    public function __construct($MilkTea)
    {
        $this->MilkTea = $MilkTea;
    }

    public function getPrice()
    {
        return $this->MilkTea->getPrice() + 5;
    }
}

看看珍珠奶茶是怎么制造出来的

// 先把奶茶做出来
$MilkTea = new MilkTea();
// 用珍珠去装饰这个奶茶,变成了波霸奶茶
$Boba = new Boba($MilkTea);
// 看看现在波霸奶茶的价格
echo $Boba->getPrice();

装饰模式使用起来还是非常容易的。在实际应用中,装饰器模式使用场景也十分广泛。

装饰器应用场景一:参数验证

api中参数验证是必不可少的,我们会选择一些已经很成熟的参数验证器,假如现在有一个非常成熟的参数验证器叫validator,我们现在用的也非常好。

突然有一天,一个需求说接口需要增加一个签名验证,然而现有的验证器不支持该功能,怎么解决呢?

第一反应可能是直接修改这个验证器,让其增加签名验证功能,但这样可不是个很好的解决方案。一个是直接修改会引入风险,一个是如果后续有更多需求,这个稳定的验证器可能会被改的一团糟,还有就是增加一个业务的需求可能让这个验证器变得有点“多事”了…

这个时候装饰器就派上用场了,不修改原有的验证器,用一个新的验证器去装饰原有的验证器就好啦!新的验证器就是为当前业务定制的专有验证器,而原来的验证器也保持了它只该有的功能。

装饰器应用场景二:日志记录

现在有一个使用了很久的日志记录类,代码写的还行,使用也很稳定,但是功能比较单一,仅仅是把日志写入一个文件就完事了。

我突发奇想想让这个日志类能够自动实现根据日期归档,这个时候我就可以写一个装饰器,这个装饰器实现了文件归档的功能,去装饰日志记录类,装饰之后的日志记录类就成为了一个带有文件归档的日志记录类。

你可能还喜欢下面这些文章

设计模式:观察者模式介绍与应用场景

简短描述在观察者模式中,有一个主题和几个观察者,主题状态改变之后会通知到所有的观察者。现实中的观察者模式观察者模式和现实世界很好对应。比如我和我的朋友们购买了今年一年的报纸,每次报社印刷了今天的报纸之后就会往我这里送。在这个系统里面,“报社“是一个主题,“我和我的朋友们”就是一个观察者,当“报社”生产报纸之后,“我和我的朋友们”都能收到这份报纸。根据上面的描述,我们很容易写出一个观察者模式。首先定义一个报社作为主题,然后定义一些用户作为观察者。不过在此之前,我们先定义主题和观察者的接口,毕竟我们是面向接口编程。定义一个主题接口namespace ds\observer\contract;/**

linux文件描述符介绍

文件描述符是什么文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习惯,因此,如

介绍一款工具,memcacheadmin,使用php制作的memcached管理监控工具

MemAdmin是一款可视化的Memcached管理与监控工具,使用PHP开发,体积小,操作简单。主要功能: 服务器参数监控:STATS、SETTINGS、ITEMS、SLABS、SIZES实时刷新 服务器性能监控:GET、DELETE、INCR、DECR、CAS等常用操作命中率实时监控 支持数据遍历,方便对存储内容进行监视 支持条件查询,筛选出满足条件的KEY或VALUE 数组、JSON等序列化字符反序列显示 兼容memcache协议的其他服务,如Tokyo Tyrant (遍历功能除外) 支持服务器连接池,多服务器管理切换方便简洁guthub地址:https://github.com/ju

布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法

引言在介绍布隆过滤器之前我们首先引入几个场景。场景一在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0。但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的key而导致的缓存被击穿?有人说, 将这个key的值置为0存入缓存不就行了吗?这是确实是一种解决方案。当访问一个不存在的key的时候,设置一个带有过期时间的标志,然后放入缓存。不过这样做的缺点也很明显:浪费内存和无法抵御随机key攻击。场景二在一个黑名单系统中,我们需要设置很多黑名单内容。比如一个邮件系统,我们需要设置黑名单用户,当判断垃圾邮件的时候,要怎么去做。比如爬虫系统,我们要记录下

设计模式:观察者模式介绍与应用场景

简短描述

在观察者模式中,有一个主题和几个观察者,主题状态改变之后会通知到所有的观察者。

现实中的观察者模式

观察者模式和现实世界很好对应。比如我和我的朋友们购买了今年一年的报纸,每次报社印刷了今天的报纸之后就会往我这里送。在这个系统里面,“报社“是一个主题,“我和我的朋友们”就是一个观察者,当“报社”生产报纸之后,“我和我的朋友们”都能收到这份报纸。

根据上面的描述,我们很容易写出一个观察者模式。首先定义一个报社作为主题,然后定义一些用户作为观察者。不过在此之前,我们先定义主题和观察者的接口,毕竟我们是面向接口编程。

定义一个主题接口

namespace ds\observer\contract;

/**
 * 主题接口
 */

interface Subject
{
    /**
     * 添加一个观察者
     * @param Observer $observer
     */
    public function add(Observer $observer);
    
    /**
     * 移除一个观察者
     * @param  Observer $observer
     * @return boolean
     */
    public function remove(Observer $observer);
    
    /**
     * 通知所有观察者
     * @param  mixed $data
     * @return boolean
     */
    public function notify($data);
}

定义一个观察者接口

namespace ds\observer\contract;

/**
 * 观察者
 */
interface Observer
{
    /**
     * 执行自己的逻辑
     * @param  mixed $data
     * @return boolean
     */
    public function execute($data);
}

定义报社,实现主题接口

namespace ds\observer;

/**
 * 报社主题
 */
class Newspaper implements contract\Subject
{
    protected $Observers;

    /**
     * 添加一个观察者
     * @param Observer $observer
     */
    public function add(contract\Observer $observer)
    {
        $hash = $this->hash($observer);
        $this->Observers[$hash] = $observer;
    }
    
    /**
     * 移除一个观察者
     * @param  Observer $observer
     */
    public function remove(contract\Observer $observer)
    {
        $hash = $this->hash($observer);
        unset($this->Observers[$hash]);
    }
    
    /**
     * 通知所有观察者
     * @param  mixed $data
     * @return boolean
     */
    public function notify($data)
    {
        foreach ($this->Observers as $observer) {
            $observer->execute($data);
        }
    }

    protected function hash($obj)
    {
        return spl_object_hash($obj);
    }
}

定义用户,实现观察者接口

namespace ds\observer;

/**
 * 用户
 */
class User implements contract\Observer
{
    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * 执行观察者自身逻辑
     * @param  string $data
     * @return void
     */
    public function execute($data)
    {
        echo $this->name . "收到了" . $data . "\n";
    }

    public function attach(contract\Subject $subject)
    {
        $subject->add($this);
    }
}

执行主流程

到目前位置,我们创建了主题-报社、观察者-用户,他们分别实现了相应的接口,看起来一切准备就绪了。接着这些代码就可以跑起来了。

namespace ds\observer;

require __DIR__ . '/contract/Observer.php';
require __DIR__ . '/contract/Subject.php';
require __DIR__ . '/Newspaper.php';
require __DIR__ . '/User.php';

// 创建一个新的报社
$newspaper = new Newspaper();

// 创建一个新的用户cmhc
$cmhc = new User('cmhc');
// 让这个用户去订阅这个报纸
$cmhc->attach($newspaper);

// 再创建一个用户xiaoming
$xiaoming = new User('xiaoming');
// 让xiaoming也订阅这个报社
$xiaoming->attach($newspaper);

// 报社今天发布了一个观察者模式,通知一下订阅我的人
$newspaper->notify('《设计模式之观察者模式》');

如果执行上面的代码,你会得到如下结果:

cmhc收到了《设计模式之观察者模式》
xiaoming收到了《设计模式之观察者模式》

可能很多人都知道观察者模式,也知道观察者模式要怎么去实现,但是却找不到具体的业务场景去使用它。但实际上观察者模式的应用是十分广泛的。下面就介绍几个经常使用观察者模式的几个场景

支付场景

在支付场景下,用户购买一件商品,当支付成功之后三方会回调自身,在这个时候系统可能会有很多需要执行的逻辑(如:更新订单状态,发送邮件通知,赠送礼品…),这些逻辑之间并没有强耦合,因此天然适合使用观察者模式去实现这些功能,当有更多的操作时,只需要添加新的观察者就能实现,完美实现了对修改关闭,对扩展开放的开闭原则。

UGC场景

在一个UGC场景下,用户发布的内容往往会经过很多流程,大部分是先发往审核系统,当审核通过之后就会出现一系列的业务逻辑,比如更新内容状态,通知给所有的粉丝等等。

你可能还喜欢下面这些文章

设计模式:装饰器模式介绍和应用

简短描述当前有一个功能完善的对象,如果我们想要给这个对象添加一个新的职责,那么我们可以用一个新的类去装饰它来实现对原有对象职责的扩展。新的类称为“装饰者”,原有的对象称为“被装饰者”。这种模式被称为装饰器模式。现实生活中的装饰器模式装饰器模式在现实生活中的例子简直太多了。比如我开了一个奶茶店,卖的是普通的奶茶。现在我想引入一个叫珍珠奶茶的商品,我要怎么做呢?我是不是需要升级一下我的制作奶茶的机器,让它支持珍珠奶茶的做法?但这种成本估计比较高,说不定还没原来做奶茶的机器好用呢!实际上我只需要买一个能做“珍珠”的机器就行!把“珍珠”放进奶茶不就成了珍珠奶茶。原有的做奶茶的机器依然可以稳定地制作做奶

linux文件描述符介绍

文件描述符是什么文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习惯,因此,如

使用php curl 的并发能力可以做什么

在php中,没有多线程让编程变得简单。但在一些需要并发提升性能的场景下,显得有些无能为力,比如发起一些http请求。但好在curl扩展可以让我们“并发”去请求网络资源。利用这个特点,我们能做很多有趣的事情。最基础的,并发请求网络资源,提升处理速度。并发访问代码<?phpclass ConcurrencyHTTP { private $_requests; private $_callbacks; private $_currentIndex = 0; public function get($url, $header = array(), $timeout = 3

介绍一款工具,memcacheadmin,使用php制作的memcached管理监控工具

MemAdmin是一款可视化的Memcached管理与监控工具,使用PHP开发,体积小,操作简单。主要功能: 服务器参数监控:STATS、SETTINGS、ITEMS、SLABS、SIZES实时刷新 服务器性能监控:GET、DELETE、INCR、DECR、CAS等常用操作命中率实时监控 支持数据遍历,方便对存储内容进行监视 支持条件查询,筛选出满足条件的KEY或VALUE 数组、JSON等序列化字符反序列显示 兼容memcache协议的其他服务,如Tokyo Tyrant (遍历功能除外) 支持服务器连接池,多服务器管理切换方便简洁guthub地址:https://github.com/ju

布隆过滤器(bloom filter)介绍以及php和redis实现布隆过滤器实现方法

引言在介绍布隆过滤器之前我们首先引入几个场景。场景一在一个高并发的计数系统中,如果一个key没有计数,此时我们应该返回0。但是访问的key不存在,相当于每次访问缓存都不起作用了。那么如何避免频繁访问数量为0的key而导致的缓存被击穿?有人说, 将这个key的值置为0存入缓存不就行了吗?这是确实是一种解决方案。当访问一个不存在的key的时候,设置一个带有过期时间的标志,然后放入缓存。不过这样做的缺点也很明显:浪费内存和无法抵御随机key攻击。场景二在一个黑名单系统中,我们需要设置很多黑名单内容。比如一个邮件系统,我们需要设置黑名单用户,当判断垃圾邮件的时候,要怎么去做。比如爬虫系统,我们要记录下