个人理解
定义:允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
刚开始接触的时候,实在不知道装饰器的意思,经过反复的查看资料,咀嚼书上的各种例子,总结出来的装饰器意思就是“用于修饰主体功能的代码叫做装饰器代码,而装饰器模式则可以用于添加新的装饰器,无需扩展子类即可实现多种装饰的主体功能(纯属个人理解,不是官方的定义)”,举个例子,假如编写一个图片上传类,那么对于图片的操作(裁剪、旋转、水印等等)就都可以作为该类的装饰器;再举个例子,假如编写一个分页类,分页类中存在一个主体功能为显示分页的页码,那么对于显示的字体大小、颜色、背景色等等都可以作为该类的装饰器。
解决了装饰器的问题,在理解装饰器模式的定义,无非就是“允许向一个现有的对象添加新的功能(主要就是添加装饰功能,可能可以添加主体功能,但是我没见过)”,在传统的代码编写的时候,如果需要新的装饰功能,都是使用继承的方式,在子类中增加额外的装饰功能。而使用了装饰器模式之后,既可以减少子类的数量,同时防止由于装饰功能过多而引起子类的臃肿,不方便维护。
介绍
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
如何解决:在不想增加很多子类的情况下扩展类。
通用的装饰器模式的UML图:
代码示例
以上面所说的分页功能类作为例子,
分页类(很早之前用的,可能代码书写不规范):
<?php
class Page
{
private $total; //总记录数
private $listRows; //每页存在记录数
private $limit; //所抽取的区间(字符串'limit 0 , 5')
private $uri; //抽取的路径地址(URL)
private $pageNum; //页数
private $listNum=4; //页数列表格数
private $config=array('header'=>'条记录','prev'=>'<','next'=>'>','first'=>'首页','last'=>'尾页'); //全局数组
private $decorator = [];
#设置装饰器
public function setDecorator($decorator)
{
$this->decorator[] = $decorator;
}
#赋予各个变量(总记录数、每页记录数、URL、当前页数$this->page、页数、limit值)值
public function __construct($total, $listRows, $parameter='')
{
$this->total=$total;
$this->listRows=$listRows;
$this->uri=$this->geturi($parameter);
$this->page=!empty($_GET["page"])?$_GET["page"]:1;
$this->pageNum=ceil($this->total/$this->listRows);
$this->limit=$this->setlimit();
}
#赋予limit值(字符串)
private function setlimit()
{
return "limit ".($this->page-1)*$this->listRows.",".$this->listRows;
}
#获取并整合URL
private function geturi($parameter)
{
//获取URL(系统变量$__SERVER[])
//查找a中是否有b strpos(a,b)
//查找连接中是否已传值
$url=$_SERVER["REQUEST_URI"].(strpos($_SERVER["REQUEST_URI"],'?')?'':'?').$parameter;
//$parse_url()解析URL,解析后为数组array(path=>xishuophp/demo.php地址,[query]=>page=10&id=5传值);
$parse=parse_url($url);
//重组URL(防止page=''重复)
if(isset($parse['query'])){
//parse_str()解析字符串(把用&连接的字符串转化维数组array([page]=>10,[id]=>5))
parse_str($parse['query'],$params);
//删除unset();删除URL中的page='';
unset($params['page']);
//组合URL与除去page=''的所传值;
//http_build_query()将一个数组转换成url问号?后面的参数字符串,并且会自动进行urlencode处理
$url=$parse['path'].'?'.http_build_query($params);
}
return $url;
}
#使私有属性limit可以被获取
public function __get($args)
{
if($args=='limit')
return $this->limit;
else
return null;
}
#本页开始记录条数
private function start()
{
if($this->total==0){
return 0;
}else{
return ($this->page-1)*$this->listRows+1;
}
}
#本页结束记录条数
public function finish()
{
return min($this->page*$this->listRows,$this->total);
}
#首页
private function first()
{
$first='';
if($this->page==1){
$first.='<a class="no_page">'.$this->config['first'].'</a>';
}else{
$first.='<a href="'.$this->uri.'&page=1" class="page">'.$this->config['first'].'</a>';
}
return $first;
}
#上一页
private function prev()
{
$prev='';
if ($this->page==1) {
$prev.='<a class="no_page">'.$this->config['prev'].'</a>';
} else {
$prev.='<a class="page" href="'.$this->uri.'&page='.($this->page-1).'">'.$this->config['prev'].'</a>';
}
return $prev;
}
#页数列表
private function pagelist()
{
$linkpage='';
$inum = floor($this->listNum/2);
for ($j=$inum; $j>=1; $j--) {
$page=$this->page-$j;
if($page<1)
continue;
$linkpage.='<a class="page" href="'.$this->uri.'&page='.$page.'">'.$page.'</a>';
}
$linkpage.='<a class="now_page">'.($this->page).'</a>';
for ($i=1;$i<=$inum;$i++) {
$page=$this->page+$i;
if ($page<=$this->pageNum) {
$linkpage.='<a class="page" href="'.$this->uri.'&page='.$page.'">'.$page.'</a>';
}else{
break;
}
}
return $linkpage;
}
#下一页
private function next()
{
$next='';
if ($this->page==$this->pageNum) {
$next.='<a class="no_page" >'.$this->config['next'].'</a>';
} else {
$next.='<a class="page" href="'.$this->uri.'&page='.($this->page+1).'">'.$this->config['next'].'</a>';
}
return $next;
}
#尾页
private function last()
{
$last='';
if ($this->page==$this->pageNum) {
$last.='<a class="no_page">'.$this->config['last'].'</a>';
} else {
$last.='<a href="'.$this->uri.'&page='.($this->pageNum).'" class="page">'.$this->config['last'].'</a>';
}
return $last;
}
#跳转页数
private function gopage()
{
return '<input type="text" class="text_page" onkeydown="javascript:if(event.keyCode==13){
var page;
if(this.value>'.$this->pageNum.'){
page='.$this->pageNum.';
}else if(this.value<1){
page=1;
}else{
page=this.value;
}
location=\''.$this->uri.'&page=\'+page+\'\'
}" value="'.$this->page.'" /><input type="button" class="button_page" value="跳转" onclick="javascript:
var page;
if(this.previousSibling.value>'.$this->pageNum.'){
page='.$this->pageNum.';
}else if(this.previousSibling.value<1){
page=1;
}else{
page=this.previousSibling.value;
}
location=\''.$this->uri.'&page=\'+page+\'\'
" />';
}
#分页方法
public function fpage($display=array(0,1,2,3,4,5,6,7,8))
{
$html[0]='<span>共有<b>'.$this->total.'</b>'.$this->config['header'].'</span>';
$html[1]='<span>每页显示<b>'.$this->listRows.'</b>条,本页<b>'.$this->start().'-'.$this->finish().'</b>条</span>';
$this->count=$this->start()-$this->finish();
$html[2]='<span>第<b>'.$this->page.'</b>页/共<b>'.$this->pageNum.'</b>页</span>';
$html[3]=$this->first();
$html[4]=$this->prev();
$html[5]=$this->pagelist();
$html[6]=$this->next();
$html[7]=$this->last();
$html[8]=$this->gopage();
$fpage='';
foreach ($this->decorator as $item) {
$fpage .= $item->mainBefore();
}
foreach($display as $index){
$fpage.=$html[$index];
}
foreach (array_reverse($this->decorator) as $item) {
$fpage .= $item->mainAfter();
}
return $fpage;
}
}
Component接口:
<?php
interface Component
{
//主体修饰前
public function mainBefore();
//主体修饰后
public function mainAfter();
}
背景装饰器:
<?php
Class BackgroundDecorator implements Component
{
private $background;
public function __construct($background)
{
$this->background = $background;
}
public function mainBefore()
{
return '<div style="background:' . $this->background . '">';
}
public function mainAfter()
{
return '</div>';
}
}
字体颜色装饰器:
<?php
Class ColorDecorator implements Component
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
public function mainBefore()
{
return '<div style="color:' . $this->color . '">';
}
public function mainAfter()
{
return '</div>';
}
}
字体大小装饰器:
<?php
Class FontSizeDecorator implements Component
{
private $size;
public function __construct($size)
{
$this->size = $size;
}
public function mainBefore()
{
return '<div style="font-size:' . $this->size . 'px">';
}
public function mainAfter()
{
return '</div>';
}
}
客户端代码(没有写自动加载,都在这里面include了):
<?php
include './Component.php';
include './Page.php';
include './decorator/ColorDecorator.php';
include './decorator/FontSizeDecorator.php';
include './decorator/BackgroundDecorator.php';
class Client
{
public function main()
{
$total = 100;
$listRows = 15;
$page = new Page($total, $listRows);
$color = new ColorDecorator("red");
$fontsize = new FontSizeDecorator('18');
//引入装饰器
$page->setDecorator($color);
$page->setDecorator($fontsize);
echo $page->fpage();
}
}
$client = new Client();
$client->main();
以上便是装饰器模式的示例代码,装饰器接口为Component,各子装饰器实现Component接口,在使用分页类之前,将需要用到的装饰器对象传入Page中。
示例UML图
上一个是之前画的,结构有些问题,下面修正的补上:
从其他资料中发现还存在不同的装饰器模式,UML图为:
如果将上面的例子套入到这个UML图中的话,那就是将Page类也同样实现Component接口,在Page中的两个继承方法中调用装饰器的方法。个人认为还是要看需求,只要满足“动态地给一个对象添加一些额外的职责”,我们就可以称之为装饰器模式,而实现的方法多种多样,不必完全相同。
下一篇
初识设计模式——代理模式与原型模式