PHP設計模式(六)—裝飾器模式(Decorator Pattern)

weixin_33724059發表於2017-05-08

裝飾器模式(Decorator Pattern): 允許向一個已有的物件新增新的功能或部分內容,同時又不改變其結構。屬於結構型模式,它是作為現有的類的一個包裝。

(一)為什麼需要裝飾器模式:

1,我們要對一個已有的物件新增新功能,又不想修改它原來的結構。

2,使用子類繼承的方法去實現新增新功能,會不可避免地出現子類過多,繼承鏈很長的情況。而且不少書籍都規勸我們竭力保持一個物件的父與子關係不超過3個。

3,裝飾器模式,可以提供對物件內容快速非侵入式地修改。

(二)裝飾器模式UML圖

5261067-1d50d0685ce057d3.jpg
Decorator Pattern

(三)簡單例項

如果有一個遊戲角色,他原來就是預設穿一件長衫。現在遊戲改進了,覺得這個角色,除了穿一件長衫前,還可以在裡面穿一件袍子,或是一件球衣。在外面穿一套盔甲或是宇航服。

<?php
/*遊戲原來的角色類
class Person{
    public function clothes(){
        echo "長衫".PHP_EOL;
    }
}
*/

//裝飾器介面
interface Decorator
{
   public function beforeDraw();
   public function afterDraw();
}
//具體裝飾器1-宇航員裝飾
class AstronautDecorator implements Decorator
{
    public function beforeDraw()
    {
        echo "穿上T恤".PHP_EOL;
    }
    function afterDraw()
    {
        echo "穿上宇航服".PHP_EOL;
        echo "穿戴完畢".PHP_EOL;
    }
}
//具體裝飾器2-警察裝飾
class PoliceDecorator implements Decorator{
    public function beforeDraw()
    {
        echo "穿上警服".PHP_EOL;
    }
    function afterDraw()
    {
        echo "穿上防彈衣".PHP_EOL;
        echo "穿戴完畢".PHP_EOL;
    }
}
//被裝飾者
class Person{
    protected $decorators = array(); //存放裝飾器
    //新增裝飾器
    public function addDecorator(Decorator $decorator)
    {
        $this->decorators[] = $decorator;
    }
    //所有裝飾器的穿長衫前方法呼叫
    public function beforeDraw()
    {
        foreach($this->decorators as $decorator)
        {
            $decorator->beforeDraw();
        }
    }
    //所有裝飾器的穿長衫後方法呼叫
    public function afterDraw()
    {
        $decorators = array_reverse($this->decorators);
        foreach($decorators as $decorator)
        {
            $decorator->afterDraw();
        }
    }
    //裝飾方法
    public function clothes(){
        $this->beforeDraw();
        echo "穿上長衫".PHP_EOL;
        $this->afterDraw();
    }
}
//警察裝飾
$police = new Person;
$police->addDecorator(new PoliceDecorator);
$police->clothes();
//宇航員裝飾
$astronaut = new Person;
$astronaut->addDecorator(new AstronautDecorator);
$astronaut->clothes();
//混搭風
$madman = new Person;
$madman->addDecorator(new PoliceDecorator);
$madman->addDecorator(new AstronautDecorator);
$madman->clothes();

當然,上面的程式碼沒有嚴格地按照UML圖,這是因為當被裝飾者只有一個時,那 Component也就是ConcreteComponent。同理,如果,只有一個裝飾器,那也沒必要實現一個implment介面。

如果我們有兩個不同的被裝飾者,那當然就應該抽象出一個Component,讓這兩個被裝飾者去繼承它。也許,那繼承不是又來了嗎。既然外面都用到繼承,直接把裝飾器的方法放到繼承裡面不就行了。

可是你想,如果,直接通過繼承的話,那裝飾過的被裝飾者就應該繼承自被裝飾者,而且被裝飾者因為裝飾的不同還要有很多不同型別的子類。而使用裝飾者模式的話,繼承鏈縮短了,而且不同的裝飾型別還可以動態增加。

相關文章