装饰器模式
又名包装(Wrapper)模式,该模式向一个已有的对象添加新的功能,而不改变其结构。
通常给对象添加功能有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 { protected $emailBody;
public function __construct(EmailBody $emailBody) { $this->emailBody = $emailBody; }
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
|
$email = new MainEmail(); $email->body();
$emailNewYear = new NewYearEmail($email); $emailNewYear->body();
$emailSpring = new SpringFestivalEmail($email); $emailSpring->body();
$emailTwo = new SpringFestivalEmail($emailNewYear); $emailTwo->body();
|