通俗易懂的談談裝飾器模式

前言

在編碼的時候,咱們爲了擴展一個類常常是使用繼承方式來實現,隨着擴展功能的增多,子類會愈來愈膨脹,使系統變得不靈活。php

裝飾器模式( Decorator Pattern )容許向一個現有的對象添加新的功能,同時又不改變其結構。它能讓咱們在擴展類的時候讓系統較好的保持靈活性。app

那麼裝飾器模式具體是什麼樣的呢?this

從一個情景開始

咱們有一塊地,在這塊地上,咱們要蓋一棟有好幾間房間的別墅,每間房間的裝修費用都不一樣,如今,咱們要對蓋別墅的費用進行計算。編碼

先定義一個Land類,表示這塊地,Land類定義了在這塊地上蓋別墅須要花錢這個規則。code

abstract class Land
{
    abstract public function cost();
}

Land已經定義好了在這塊地上蓋房須要花錢的這個規則了,可是蓋一間房間具體花多少錢呢?對象

此時咱們再定義一個Room類,這個類具體的定義了一個房間建造的基本費用(一個最簡單房間,裏面啥也沒有的)。繼承

class Room extends Land
{
    private $money = 1000;
    public function cost()
    {
        return $this->money;
    }
}

而後開始建造房間,咱們建了兩個房間,分別是客廳和餐廳,用LivingRoom類和DiningRoom類來表示io

class LivingRoom extends Room
{
    public function cost()
    {
        return parent::cost()+200; //客廳的建造費用在房屋建造費用的基礎上多200,好比要買沙發,電視
    }
    
}

class DiningRoom extends Room
{
    public function cost()
    {
        return parent::cost()+100; //餐廳的建造費用在房屋建造費用的基礎上多100,好比買餐桌
    }
}

如今,咱們很容易就能獲得建造一間客廳所需的花費function

$livingRoomCost = new LivingRoom();
echo $livingRoomCost->cost();

問題的產生

不過,這樣的結構並不具有靈活性,雖然咱們能夠很容易的分別得出建造一間客廳和建造一間餐廳的費用,可是,若是我買的地比較小,只能把餐廳和客廳建在同一個房間裏,那要怎麼去計算費用?難道還要很麻煩的去建立一個包含客廳和餐廳的LivingDiningRoom類?這樣作的話除了麻煩,還會使代碼產生重複。class

解決問題

爲了更好的解決這個問題,咱們得作一些調整,一樣先聲明Land類和Room類,不一樣的是,引入了一個房間的裝飾類RoomDecorator,它繼承了Land類,由於沒有實現Land類的cost()方法,因此需將它聲明爲抽象類,而且定義了一個以Land類的對象爲參數的構造方法,傳入的對象會保存在$land屬性中,該屬性聲明爲protected,以便子類訪問。具體以下。

abstract class RoomDecorator extends Land
{
    protected $land;
    public function __construct(Land $land)
    {
        $this->land = $land;
    }
}

而後咱們再從新定義客廳類和餐廳類

class LivingRoom extends RoomDecorator
{
    public function cost()
    {
        return $this->land->cost()+200;
    }
}


class DiningRoom extends RoomDecorator
{
    public function cost()
    {
        return $this->land->cost()+100;
    }
}

這兩個類都擴展自RoomDecorator類,這意味着它們都擁有指向Land對象的引用。當它們的cost()方法被調用時,都會先調用所引用的Land類對象的cost()方法,而後再執行本身特有的操做。

因此這時候,建造一間客廳所需的費用是這樣計算

$livingRoomCost = new LivingRoom(new Room());
echo $livingRoomCost->cost(); //輸出1200

建造一間餐廳所需的費用是這樣計算

$diningRoomCost = new DiningRoom(new Room());
echo $diningRoomCost->cost(); //輸出1100

回到剛纔的問題,若是咱們需計算建造一間包含客廳餐廳的房間所需費用,代碼以下

$livingRoom = new DiningRoom(new LivingRoom(new Room()));
echo $livingRoom->cost(); //輸出1300

看,咱們如今計算建造費用的思路是:計算出基礎房間的費用 --> 在基礎房間上裝飾成客廳的費用 --> 在客廳的基礎上加裝飾餐廳的費用 --> 獲得包含客廳餐廳的房間費用。已經不須要麻煩的經過建立一個LivingDiningRoom類來計算包含客廳餐廳的房間建造費用了。

這即是裝飾模式,經過一層一層的裝飾,咱們能夠靈活的獲得咱們想要的結果。能夠輕鬆的添加新的裝飾器類或者新的組件來建立靈活的結構。

完整代碼

<?php

abstract class Land
{
    abstract function cost();
}

class Room extends Land
{
    private $money = 1000;
    public function cost()
    {
        return $this->money;
    }
}

//裝飾器
abstract class RoomDecorator extends Land
{
    protected $land;
    public function __construct(Land $land)
    {
        $this->land = $land;
    }
}

class LivingRoom extends RoomDecorator
{
    public function cost()
    {
        return $this->land->cost()+200;
    }
}


class DiningRoom extends RoomDecorator
{
    public function cost()
    {
        return $this->land->cost()+100;
    }
}

$livingRoomCost = new LivingRoom(new Room());
echo $livingRoomCost->cost(); //輸出1200

$diningRoomCost = new DiningRoom(new Room());
echo $diningRoomCost->cost(); //輸出1100

$livingDining = new DiningRoom(new LivingRoom(new Room()));
echo $livingDining->cost(); //輸出1300

the end.

happy coding! ^_^

相關文章
相關標籤/搜索