装饰器模式

装饰器模式

又名包装(Wrapper)模式,该模式向一个已有的对象添加新的功能,而不改变其结构。

通常给对象添加功能有3中方法:

  1. 直接修改类,添加相应的功能
  2. 派生对应的子类扩展新功能
  3. 使用对象组合的方式

直接修改类代码是一种侵入式修改,很容易对类功能造成损害。

使用继承方式扩展功能,则在整个子类中添加功能,即使有的对象不需要,new出来也会有这些新功能。

装饰器模式是典型的基于对象组合的方式,可以很灵活的给对象添加所需要的功能,

它能动态的为一个对象增加功能,而且还能动态撤销。

继承不能做到这一点,继承的功能是静态的,不能动态增删。

1 问题

假设我们有一个邮件内容模板类,如下,一般情况下我们都用这个模板发送邮件:

1
2
3
4
5
6
7
8

class emailBody
{
public function body()
{
echo "公司准备为您加薪50%。\n";
}
}

现在元旦准备来了,我们想在邮件模板上加一些元旦快乐的祝福语。

这时,我们可以直接修改emailBody类,但是这样实在不好。

所以,选择用子类继承方式来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13

class newYearEmail extends emailBody
{
public function body()
{
echo '元旦快乐!!!';
parent::body();
}
}

$email = new newYearEmail();
$email->body();

再过一个多月,春节来了,我们又可以按照元旦的做法,添加一个springFestivalEmail类。

可是,如果想在不修改原来类的基础上,同时祝福元旦快乐和春节快乐呢?

这时我们不禁会想,这种情况继承方式是否好用。

2 解决

从上面的问题中,我们看到,我们可以用子类的方式为类扩展一个功能,

但是如果要同时增加多个功能时,会变得冗长而复杂,这是装饰器模式要解决的问题。

首先,创建一个接口,用以规范邮件类:

1
2
3
4
5
6
7
8
9

/**
* 邮件内容接口,规范实现类
*/
interface EmailBody
{
public function body();
}

然后是正常的邮件内容类,我们用装饰器的目的就是,在某些情况下不改变其代码,也能得到不同的结果。

1
2
3
4
5
6
7
8
9
10
11
/**
* 正常邮件内容类
*/
class MainEmail implements EmailBody
{
public function body()
{
echo "公司准备为您加薪50%。\n";
}
}

然后是主装饰器类,这个类用属性保存MainEmail类的对象,然后根据需要改变它的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* 邮件内容装饰器类
*
*/
abstract class EmailBodyDecorator implements EmailBody
{
// 保存MainEmail类对象
protected $emailBody;

// 实例化这个类或者子类时,必须传入一个被修饰的对象
public function __construct(EmailBody $emailBody)
{
$this->emailBody = $emailBody;
}

// 用抽象方法声明EmailBody规定的方法,
// 在子类中用来改变MainEmail对象的行为
abstract public function body();
}

然后我们定义两个装饰器的子类,在这两个子类里面我们改变原MainEmail的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

class NewYearEmail extends EmailBodyDecorator
{
public function body()
{
echo '元旦快乐!';
$this->emailBody->body();
}
}

class SpringFestivalEmail extends EmailBodyDecorator
{
public function body()
{
echo '春节快乐!';
$this->emailBody->body();
}
}

客户端使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 【正常】发送邮件
* 输出: 公司准备为您加薪50%。
*/
$email = new MainEmail();
$email->body();

/**
* 发送有【元旦】祝福的邮件
* 输出: 元旦快乐!公司准备为您加薪50%。
*/
$emailNewYear = new NewYearEmail($email);
$emailNewYear->body();

/**
* 发送有【春节】祝福的邮件
* 输出: 春节快乐!公司准备为您加薪50%。
*/
$emailSpring = new SpringFestivalEmail($email);
$emailSpring->body();

/**
* 发送同时有【元旦】和【春节】祝福的邮件
* 输出: 春节快乐!元旦快乐!公司准备为您加薪50%。
*/
$emailTwo = new SpringFestivalEmail($emailNewYear);
$emailTwo->body();