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

简短描述

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

现实生活中的装饰器模式

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

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

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

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

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,我们现在用的也非常好。

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

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

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

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

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

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

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

MySQL开启慢查询日志以及慢查询日志分析

mysql满查询有助于让我们发现系统中瓶颈所在。开启方法默认情况满查询应该关闭,如果需要分析则需要手动开启。mysql> show variables like '%slow_query_log%'; +---------------------+--------------------------------------+ | Variable_name | Value | +---------------------+--------------------------------------+ | slow_

awk分析nginx日志中的网页响应时间

nginx日志可以十分方便的看到每一个请求的响应速度,通常我会用awk去分析这些请求耗时。通常nginx的log配置是这样的log_format access_comment '$remote_addr - $remote_user "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for ' '$upstream_response_time $request_time';我们记录的日志类似于这样127.0.0.1 - - "POST /get" "M

Redis持久化

在一个高并发,但是数据量不大的系统中,使用Redis做数据库再好不过,结合Swoole,只需要很少的机器就能抗住很大的量。Redis大多数的应用可能都是当做缓存,当作为一个数据库用的时候,就必须要考虑持久化的问题了。持久化的意思就是将内存中的数据写到磁盘中,当再次重启之后,数据可以从磁盘中进行恢复,不会丢失。Redis持久化有两个策略,一个是RDB快照,一个AOF日志,不管是什么策略,最终的目的都是将数据保存在磁盘上,并不高深。只需要耐心的看看这两种策略,就能明白了。RDB快照从名字上我们就能知道这是RedisDB的缩写了,Redis快照是这样生成的,到了需要生成快照的时候,通过fork当前进

gcc/g++编译参数详解

编译步骤gcc 与 g++ 分别是 gnu 的 c & c++ 编译器。gcc/g++ 在执行编译工作的时候,总共需要4步:预处理,生成 .i 的文件将预处理后的文件转换成汇编语言, 生成文件 .s 有汇编变为目标代码(机器代码)生成 .o 的文件连接目标代码, 生成可执行程序 参数详解-x language filename参数含义为指定文件所使用的语言。根据约定,C语言的后缀名称为".c",而 C++ 的后缀名为".cpp"或".cc",但如果你的源代码后缀不约定的那几种,那么需要使用-x参数来指定文件所使用的语言。这个参数对他后面的文件名都起作用。 可以使用的参数吗有下面的这些:

centos7系统初初始化工作以及网站环境搭建(php7+nginx+mysql)

拿到一台做网站的主机, 我们先要做一些环境初始化的工作, 由于这些工作会有些繁琐,因此记录一下. 后面将这些流程写成一个shell脚本,一次性完成.此次工作流程如下: 安全性设置 额外的目录创建 网站环境搭建安全性设置一般从某云上买的主机, 默认账户是root, 为了不被暴力破解, 我们首先需要设置一个强一点的密码,不过更好的方法是禁用root, 另外创建一个用户来作为日常管理的账户.第一步: 创建一个新的账户,并且能够切换到root权限比如我的用户名叫xiaobai, 添加用户名就是useradd xiaobai设置密码passwd xiaobai之后输入密码,一个新的账户就设定好了.

使用crontab+rsync备份你的站点

这是一篇记录站点备份的文章,通常站点需要每隔一周,甚至更短的时间进行备份,然而手工备份再下载下来这实在是麻烦,于是利用crontab和rsync进行站点备份,再利用我本地的树莓派定时拉取备份结果,想想还是挺完美的。编写备份的shell站点文件备份很简单,直接将站点打包压缩即可,但是想了想还是不把站点路径定义死,而是使用一个参数将站点路径传递进去,这样就能写一个通用的文件备份#! /bin/bashsrc='';dest='';while getopts 's:d:' OPTdo case $OPT in s) src=$OPTARG;; d) dest=$OPTARG;; esacdonei

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

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

C++动态内存管理

C++中,动态内存管理是通过一对运算符来完成:new 和 delete。new操作符在内存中为对象分配空间并返回一个指向该对象的指针,delete接收一个动态对象的指针,销毁该对象,并释放与之相关的内存。手动管理内存看起来只有这两个操作,似乎很轻松,但实际上这是一件非常繁琐的事情,分配了内存但没有释放内存的场景发生的概率太大了!回想一下,你有多少次打开抽屉却没关上,拿出来的护肤品擦完脸之后却忘了放回去,吃完饭却忘了洗碗。类似这种没有收尾的事情我做的太多了。(以上这些都是在实际生活中我爱人批评我的点)我连这种明面上的事情都能忘记收尾,何况分配内存!所以为了世界和平,我放弃了手动管理内存。好在C+

ftp传输binary和ascii模式(二进制和文本)的区别

ASCII模式和BINARY模式的区别是回车换行的处理,binary模式不对数据进行任何处理,asci模式将回车换行转换为本机的回车字符,比如Unix下是\n,Windows下是\r\n,Mac下是\rascii模式下会转换文件不能说是不同系统对回车换行解释不同,而是不同的系统有不同的行结束符unix系统下行结束符是一个字节,即十六进制的0A,而ms的系统是两个字节,即十六进制的0D0A所以当你用ascii方式从unix的ftp server下载文件时(不管是二进制或者文本文件),每检测到一个字节是0A,就会自动插入一个0D,所以如果你的文件是二进制文件比如可执行文件、压缩包什么的,就肯定不能

如何用火焰图分析程序性能瓶颈

一个经验非常丰富的程序员可能可以静态分析程序的性能瓶颈,但大多数人做不到这些。这个时候我们往往需要借助一些辅助工具来分析程序的性能瓶颈,火焰图就是其中之一。一张典型的火焰图长这样本文主要介绍如何生成和分析火焰图。如何生成火焰图第一步:使用perf生成采样日志火焰图是根据perf命令生成的日志文件来生成的,所以首先需要使用perf命令对所需要分析的进程进行采样。命令解释:运行上面两个命令之后,会生成一个out.perf文件第二步:下载FlameGraphFlameGraph用于将采样日志转换成svg图片,这个图片就是我们见到的火焰图。FlameGraph地址:https://github.com

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

简短描述

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

现实中的观察者模式

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

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

定义一个主题接口

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

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

Go入门:四、面向对象

这是我的Go学习笔记的第四篇,面向对象!现代语言几乎都会面向对象进行了支持!当然,Go也具备面向对象的特性!我的语言学习过程一般分为下面几个:1. 变量和数据类型2. 流程控制方法3. 函数声明和调用4. 面向对象5. 语言特性6. 标准库Go语言中的面向对象有点特殊。在Go语言里面,没有显式的class、extends等面向对象语言经常使用的关键词,但是却有面向对象的特性。看看Go怎么实现的把!创建一个类按照我的理解,类实际上就是某种模板,这个模板里面含有有限多个属性和方法。在Go里面,定义这个模板的语法使用type来实现!比如单个int类型可以构成一个类(没错,你甚至可以在int数据类型上

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

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

signal函数详解

signal作用是为信号注册一个处理器。这里的“信号”是软中断信号,这种信号来源主要有三种:程序错误:比如除0,非法内存访问。外部信号:终端Ctrl-C产生的SIGINT信号,定时器产生的SIGALERM。显示请求:kill函数发送的任意信号。当kill一个进程的时候,默认会发送SIGTERM信号,此时这个信号只有默认处理操作(SIG_DFL),直接中断进程执行。如果此时该进程正在执行一个任务,直接终止该进程会导致任务没有完成。这个时候为SIGTERM信号注册一个信号处理函数就十分有必要。介绍参数sig要设置信号处理函数的信号。它可以是实现定义值或下例值之一:SIGABRTSIGFPESIGI

centos7系统初初始化工作以及网站环境搭建(php7+nginx+mysql)

拿到一台做网站的主机, 我们先要做一些环境初始化的工作, 由于这些工作会有些繁琐,因此记录一下. 后面将这些流程写成一个shell脚本,一次性完成.此次工作流程如下: 安全性设置 额外的目录创建 网站环境搭建安全性设置一般从某云上买的主机, 默认账户是root, 为了不被暴力破解, 我们首先需要设置一个强一点的密码,不过更好的方法是禁用root, 另外创建一个用户来作为日常管理的账户.第一步: 创建一个新的账户,并且能够切换到root权限比如我的用户名叫xiaobai, 添加用户名就是useradd xiaobai设置密码passwd xiaobai之后输入密码,一个新的账户就设定好了.

ftp传输binary和ascii模式(二进制和文本)的区别

ASCII模式和BINARY模式的区别是回车换行的处理,binary模式不对数据进行任何处理,asci模式将回车换行转换为本机的回车字符,比如Unix下是\n,Windows下是\r\n,Mac下是\rascii模式下会转换文件不能说是不同系统对回车换行解释不同,而是不同的系统有不同的行结束符unix系统下行结束符是一个字节,即十六进制的0A,而ms的系统是两个字节,即十六进制的0D0A所以当你用ascii方式从unix的ftp server下载文件时(不管是二进制或者文本文件),每检测到一个字节是0A,就会自动插入一个0D,所以如果你的文件是二进制文件比如可执行文件、压缩包什么的,就肯定不能

股票获取接口

最近开始研究股票了,自己一个一个的去看,几千支股票完全看不过来啊,想着自己写一个程序,让程序来看股票吧!股票接口首先我们需要得到所有的股票代码,好在已经有网页帮我们列出了所有的股票名称和代码,地址是:http://quote.eastmoney.com/stocklist.html通过这个页面,就可以抓取了。抓取之后我们就可以存入mysql中,每一个股票可以存一张表,而每一张表中则可以存入股票的动态数据。这里我们只能获取到一些最简单的数据,一些更加详细的数据还需要获取,这里需要使用一个腾讯财经的接口http://qt.gtimg.cn/q=sz000858该接口为获取五粮液的股票数据,返回结果

imajax-single,一款全站ajax的博客主题

近半年来,慢慢的制作一款能够适合博客使用的,能够方便阅读的,速度要快主题。灵感来自于wordpress的官方默认主题。制作缘由很喜欢wordpress的默认主题twenty-fifteen,但是不是全站ajax,曾经为twenty-fifteen增加过诸多功能,但是使用起来还是特别的不方便,因此就自己制作了一份主题了。制作过程断断续续的几个月,有时间就写一点代码,没时间就放着。由于博主现在主要搞后端接口了,所以在前端方面花的功夫就太少了,主题外观感觉看起来中规中矩,不过使用起来应该还是很贴心的。主题外观典型的双栏主题,侧栏在左,内容在右。如果你只是想要一个博客,并且喜欢整理你日常的生活内容,那

记一次进程异常退出的问题排查

机器搬家之后,之前一直稳定的PHP多进程程序子进程突然异常退出,但是退出的不是很频繁,查看进程日志并也没有发现有什么导致退出的,问题比较诡异。于是开启了一段问题排查之路。首先查看内核日志,使用dmesg,拉到最后发现有一些这样的错误,看来确实是崩溃了。 php: segfault at 7f6443ee18c8 ip 00007f6443ee18c8 sp 00007fff4d4ba818 error 15 in libc-2.17.so php: segfault at 0 ip 000000000075919d sp 00007fff0c6e0578 error 4 in php trap

使用apidoc为你的项目编写api文档

在使用apidoc之前,我一直使用wiki来写文档,后来发现这种方式更新起来比较痛苦,时间一长甚至就忘记了更新了。一直在寻找能够使用注释直接生成文档的程序。某一天同事推荐了apidoc,发现这正是我想要的工具。apidoc原理apidoc的原理是扫描你的代码文件,提取出注释部分,根据一些规则生成相应的文档。默认的模板久很美观,十分适合作为api文档的生成器。目前apidoc支持的注释基本涵盖了大部分语言的风格了,c,java,php,js,python,perl,lua, Erlang...安装需要使用npm安装,如果没有安装npm,请先去https://www.npmjs.com/下载npm

创建自己的composer包之怎样制作composer包

项目做多了之后,可能会慢慢总结出自己的代码库出来,当在新的项目中使用的时候,总不能一直是复制粘贴。这个时候,composer就能派上用场了。一个项目开始,使用composer就能够加载所需要的依赖,非常方便。这个时候,来做一个自己的包吧!使用命名空间composer自动加载需要用到命名空间,因此所有的代码库都需要使用命名空间,如果没有,那就改吧!使用命名空间之后你可能会打开新世界的大门。创建composer.json我假设你已经安装了composer,并且已经会使用了。创建自己的包我们首先需要创建一个composer.json,示例文件如下上面的composer.json有一个比较重要的是au