初识设计模式——装饰器模式

个人理解

定义:允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

刚开始接触的时候,实在不知道装饰器的意思,经过反复的查看资料,咀嚼书上的各种例子,总结出来的装饰器意思就是“用于修饰主体功能的代码叫做装饰器代码,而装饰器模式则可以用于添加新的装饰器,无需扩展子类即可实现多种装饰的主体功能(纯属个人理解,不是官方的定义)”,举个例子,假如编写一个图片上传类,那么对于图片的操作(裁剪、旋转、水印等等)就都可以作为该类的装饰器;再举个例子,假如编写一个分页类,分页类中存在一个主体功能为显示分页的页码,那么对于显示的字体大小、颜色、背景色等等都可以作为该类的装饰器。

解决了装饰器的问题,在理解装饰器模式的定义,无非就是“允许向一个现有的对象添加新的功能(主要就是添加装饰功能,可能可以添加主体功能,但是我没见过)”,在传统的代码编写的时候,如果需要新的装饰功能,都是使用继承的方式,在子类中增加额外的装饰功能。而使用了装饰器模式之后,既可以减少子类的数量,同时防止由于装饰功能过多而引起子类的臃肿,不方便维护。

介绍

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

如何解决:在不想增加很多子类的情况下扩展类。

通用的装饰器模式的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接口:

扫描二维码关注公众号,回复: 3490726 查看本文章
<?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中的两个继承方法中调用装饰器的方法。个人认为还是要看需求,只要满足“动态地给一个对象添加一些额外的职责”,我们就可以称之为装饰器模式,而实现的方法多种多样,不必完全相同。

下一篇

初识设计模式——代理模式与原型模式

猜你喜欢

转载自blog.csdn.net/cuixiaogang110/article/details/82845733