php设计模式(4)-- 装饰器模式_PHP_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > PHP > php设计模式(4)-- 装饰器模式

php设计模式(4)-- 装饰器模式

 2017/12/8 17:47:39  xieye  程序员俱乐部  我要评论(0)
  • 摘要:我的设计模式系列文章php设计模式(1)--观察者模式--spl标准写法php设计模式(2)--观察者模式--用trait来改进的写法php设计模式(3)--责任链(职责链)模式php设计模式(4)--装饰器模式分析网上的套话就不说了。图片来自红黑联盟:上图中,Componet对应我这里的DisplayConcreteComponet对应我这里的BasicDisplayDecorator对应我这里的Border剩下两个分别对应FullBorder和SiderBorder装饰器适用场合
  • 标签:PHP 模式 设计 设计模式
我的设计模式系列文章
php设计模式(1)-- 观察者模式 -- spl标准写法
php设计模式(2)-- 观察者模式 -- 用trait来改进的写法
php设计模式(3)-- 责任链(职责链)模式
php设计模式(4)-- 装饰器模式

分析

网上的套话就不说了。

图片来自红黑联盟:


上图中,Componet 对应我这里的 Display
ConcreteComponet 对应我这里的 BasicDisplay
Decorator 对应我这里的 Border
剩下两个分别对应 FullBorder 和 SiderBorder

装饰器适用场合:假设一个对象有某种功能,在有些时候,需要对这功能增强,但有些时候,又需要使用原有功能。这时使用装饰器。

装饰器和责任链都会有一个长长的对象关联链条,其差异是:责任链强调对同一个消息的不同处理,而不是功能增强。责任链是多个功能,而装饰器是一个功能,但是这个功能有时需要一些小改变,改来改去是同一个功能

java的流使用了装饰器,说明装饰器也可以改变结果的表现形式,总之,功能增强是个泛泛而言的事情,能做到事情很多。

注意:和观察者,以及责任链一样,类与类的具体哪个和哪个关联,先后顺序等,都放在客户端代码里!

现在我们构造需求。

假设,我们有一个文档,需要打印机打印出来,但是我们希望打印一些边框,让文档漂亮些,边框跟文档内容没关系。但是同一个功能:打印。所以我们使用装饰器。

代码实现

class="php">
<?php
/**
 * 装饰器模式学习代码。
 * 
 * 位于最顶层,表示了整个设计模式示例的功能:打印字符串
 * 
 * 这个程序也可以包装多行的文本,只是代码改得复杂一些,不利于看清设计模式。
 */
abstract class Display
{
    public abstract function getColumns();    //取得横向的字数,把责任委托给子类,所以抽象,下同
    //观察子类可知,只要有一个类使用到了,
    //需要所有的类都要有这个方法!

    public abstract function getRows();       //取得纵向的行数,把责任委托给子类
    public abstract function getRowText($row);//取得第row行的字符串

    public function show()  {                  //因为这个方法的实现是固定的,所以写这里
        for ($i = 0; $i < $this->getRows(); $i++) {
            echo $this->getRowText($i) . PHP_EOL;
        }
        echo PHP_EOL;
    }
}

/**
 * 注意此类一定被包裹在核心,和别的类不同,虽然都是继承Display类
 * 所以我取名basic
 */
class BasicDisplay extends Display
{
    private $string;                     //最里面的,一定会被打印出的字符串

    public function __construct($string) { //需要在外部指定
        $this->string = $string;
    }

    public function getColumns() {        //注意!,仅被某类调用,却写到每个类中!!
        return strlen($this->string);
    }

    public function getRows() {            //核心只打印一行
        return 1;
    }

    public  function getRowText($row) {   //仅在row为0时才返回
        if ($row == 0) {
            return $this->string;
        } else {
            return null;
        }
    }

}


/**
 * 因为外框又有多种,所以把共性抽取出来,形成此抽象类,其中,
 * 还确定了每个装饰器子类都有的构造方法和属性,通常就是属于共同接口的对象
 */
abstract class Border extends Display
{
    protected $display;                             //注意到:是同一接口的对象,php
    //不像java能表达出类型,但实际是的

    protected function __construct(Display $display) { //后面可看到,子类实际可以扩展构造方法
        $this->display = $display;
    }
}

/**
 * 在字符两边输出特定字符(由程序外部指定)的外框类,
 * 通过Border间接继承Display
 *
 */
class SideBorder extends Border
{
    //装饰用的字符,会写到两边
    private $borderChar;                              

    public function __construct(Display $display, $ch) {//注意重写了构造方法。
        parent::__construct($display);
        $this->borderChar = $ch;
    }

    public function getColumns() {// 左右各加一个字符,所以宽度加2
        return 1+ $this->display->getColumns() + 1;
    }

    public function getRows() {
        return $this->display->getRows();
    }

    /**
     * 最后的显示效果如 |hello, world|
     * 其中两边的|只是示例,由外部传入的。
     * 根据php的类型,没有字符类,所以请确保只传入一个字符。这里没有判断,也可以抛异常等。
     */
    public function getRowText($row)  { // 注意这其实在一个循环里,只是每行做同样的处理罢了。
        return $this->borderChar . $this->display->getRowText($row) . $this->borderChar;
    }
}

/**
 * 把字符包裹于其中的外框类
 * 通过Border间接继承Display
 *
 */
class FullBorder extends Border
{
    private $borderChar;

    public function __construct(Display $display) {
        parent::__construct($display);
    }

    //这些方法很重要,保证了上下的字符对齐(假定字符宽度相等)
    //注意到:虽然别的类的该方法似乎没有用到,
    //实际在这里用到了,让本类可以知道里面内核的字符宽度
    public function getColumns() {
        return 1 + $this->display->getColumns() + 1;
    }

    public function getRows() {
        return 1 + $this->display->getRows() + 1;
    }

    /**
     * 把行数确定为核心内容加2后,见上getRows,就可以在顶部和底部输出装饰
     * +-------------------+
     * +-------------------+
     *
     * 然后,在内容的两边输出 | 字符
     */
    public function getRowText($row) {
        if ($row == 0) { // 第1行
            return '+' . $this->makeLine('-', $this->display->getColumns()) . '+';
        } elseif ($row == $this->display->getRows() + 1) { // 最后一行,= 原有总行数 + 1,因为行数从1算,row从0算。
            return '+' . $this->makeLine('-', $this->display->getColumns()) . '+';
        } else {
            return '|' . $this->display->getRowText($row - 1) . '|';//-1 是因为有一个错位,多了一行
        }
    }

    private function makeLine($ch, $count)  {
        $s = '';
        for ($i = 0; $i < $count; $i++) {
            $s .= $ch;
        }
        return $s;
    }

}

//打印“Hello,world”,没有任何装饰
$b1 = new BasicDisplay('Hello, world.');
$b1->show();

//把装饰字符'#'加在b1的左右两边
$b2 = new SideBorder($b1, '#');
$b2->show();

//把b2加上装饰外框
$b3 = new FullBorder($b2);
$b3->show();

//b4在核心的外面加上了多重外框,请仔细观察图形与每个装饰器的对应关系,很有意思的。
$b4 = new SideBorder(
          new FullBorder(
              new FullBorder(
                  new SideBorder(
                      new FullBorder(
                          new BasicDisplay('Hello, world.')
                      ), '*'
                  )
              )
          ), '/'
      );
$b4->show();


结果展示,非常精巧
Hello, world.

#Hello, world.#

+---------------+
|#Hello, world.#|
+---------------+

/+-------------------+/
/|+-----------------+|/
/||*+-------------+*||/
/||*|Hello, world.|*||/
/||*+-------------+*||/
/|+-----------------+|/
/+-------------------+/



  • 大小: 26.7 KB
  • 查看图片附件
发表评论
用户名: 匿名