PHP結構型設計模式(上)

fangle發表於2017-05-20

結構型設計模式: GOF 23個設計模式中,屬於結構型設計模式7個。分別為介面卡模式裝飾器模式代理模式外觀模式橋接模式組合模式享元模式


PHP設計模式(五)—介面卡模式(Adapter Pattern)

介面卡模式(Adapter Pattern):將某個物件的介面適配為另一個物件所期望的介面。屬於結構型設計模式。

(一)為什麼需要介面卡模式

1,某個運算元據庫的有兩套不同的資料庫操作方法,我們通過介面卡統一成一個介面。例如,我們待會把mysql和mysqli統一成一個介面。

2,我們有多套資料庫對應了多種資料庫操作,例如MySQL,SqlServer,Oralce,Redis都有對應的操作函式,或操作類。PDO把這些都統一成一個介面。

3,系統的增加一些新功能,建立了一個新的介面,但是老的介面並不想廢棄。可以使用介面卡模式,對使用者隱藏這兩個介面,提供使用者所希望的介面。

(二)介面卡UML圖

Adapter Pattern

(三)設計例項

把MySQL和mysqli統一成一個介面,使用者可以呼叫同樣的方法使用MySQL和mysqli運算元據庫。

<?php
//MySQL待操作適配類
class MySQLAdaptee implements Target
{
    protected $conn;    //用於存放資料庫連線控制程式碼
    //實現連線方法
    public function connect($host, $user, $passwd, $dbname)
    {
        $conn = mysql_connect($host, $user, $passwd);
        mysql_select_db($dbname, $conn);
        $this->conn = $conn;
    }
    //查詢方法
    public function query($sql)
    {
        $res = mysql_query($sql, $this->conn);
        return $res;
    }
    //關閉方法
    public function close()
    {
        mysql_close($this->conn);
    }
}
//MySQLi操作待適配類
class MySQLiAdaptee 
{
    protected $conn;
    public function connect($host, $user, $passwd, $dbname)
    {
        $conn = mysqli_connect($host, $user, $passwd, $dbname);
        $this->conn = $conn;
    }
    public function query($sql)
    {
        return mysqli_query($this->conn, $sql);
    }
    public function close()
    {
        mysqli_close($this->conn);
    }
}
//使用者所期待的介面
Interface Target{
    public function connect($host, $user, $passwd, $dbname);
    public function query($sql);
    public function close();
}
//使用者期待適配類
Class DataBase implements Target {
    protected $db ;     //存放MySQLiAdapter物件或MySQLAdapter物件
    public function  __construct($type){
        $type = $type."Adapter" ;
        $this->db = new $type ;
    }
    public function connect($host, $user, $passwd, $dbname){
        $this->db->connect($host, $user, $passwd, $dbname);
    }
    public function query($sql){
        return $this->db->query($sql);
    }
    public function close(){
        $this->db->close();
    }
}
//使用者呼叫同一個介面,使用MySQL和mysqli這兩套不同示例。
$db1 = new DataBase('MySQL');
$db1->connect('127.0.0.1','root','1234','myDB');die;
$db1->query('select * from test');
$db1->close();

$db2 = new DataBase('MySQLi');
$db2->connect('127.0.0.1','root','1234','myDB');
$db2->query('select * from test');
$db2->close();
複製程式碼

上面的程式碼只是一個示例,如果你執行以上的程式碼報了mysql函式不存在或是被廢棄的錯誤。這是正常的,因為MySQL這套函式在PHP5.5以上的版本已經被廢棄了。感興趣的還可以去了解一下PDO的實現。 通過上面的程式碼,我們可以看到,使用介面卡可以把不同的操作介面封裝起來,對外顯示成使用者所期望的介面。

這就好比你家牆上有一個電源三相插孔,但是插孔的孔距之間太小。你的電器三相插頭插腳距太大的插不進去,或許你還有個兩相的插頭,或許你還有條USB線和type-C線,這些都沒法插到三相介面裡。於是你買了個插腳適合插到你牆上的排插,然後這個排插是這些年新出的,USB也能插。於是你把你的三相插頭,兩相插頭,USB線,type-c線都插到排插上。實際上就是間接地連在了你牆壁上的三相插孔上。

沒錯,介面卡要做的就是這麼回事。

有些書也把介面卡模式分為:類的介面卡模式,物件的介面卡模式,介面的介面卡模式


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

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

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

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

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

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

(二)裝飾器模式UML圖

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,讓這兩個被裝飾者去繼承它。也許,那繼承不是又來了嗎。既然外面都用到繼承,直接把裝飾器的方法放到繼承裡面不就行了。

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


PHP設計模式(七)—代理模式(Proxy Pattern)

代理模式(Proxy Pattern):構建了透明置於兩個不同物件之內的一個物件,從而能夠擷取或代理這兩個物件間的通訊或訪問。

(一)為什麼需要代理模式

1,遠端代理,也就是為了一個物件在不同地址空間提供區域性代表。隱藏一個物件存在於不同地址空間的事實。

2,虛擬代理,根據需要來建立開銷很大的物件,通過它來存放例項化需要很長時間的真實物件。

3,安全代理,用來控制真實物件的訪問物件。

4,智慧指引,當呼叫真實物件的時候,代理處理一些事情。

(二)代理模式UML圖

Proxy Pattern

(三)簡單例項

案例一:你想買一張學友哥的新唱片,以前你都是在縣城CD店裡買的。現在CD行業不景氣,沒得賣了。你只能找人去香港幫你代購一張。

<?php
//代理抽象介面
interface shop{
    public function buy($title);
}
//原來的CD商店,被代理物件
class CDshop implements shop{
    public function buy($title){
        echo "購買成功,這是你的《{$title}》唱片".PHP_EOL;
    }
}
//CD代理
class Proxy implements shop{
    public function buy($title){
        $this->go();
        $CDshop = new CDshop;
        $CDshop->buy($title);
    }
    public function go(){
        echo "跑去香港代購".PHP_EOL;
    }
}

//你93年買了張 吻別
$CDshop = new CDshop;
$CDshop->buy("吻別");
//14年你想買張 醒著做夢 找不到CD商店了,和做夢似的,不得不找了個代理去香港幫你代購。
$proxy = new Proxy;
$proxy->buy("醒著做夢");
複製程式碼

案例二:通過代理實現MySQL的讀寫分離,如果是讀操作,就連線127.0.0.1的資料庫,寫操作就讀取127.0.0.2的資料庫

<?php
class Proxy
{   
    protected $reader;
    protected $wirter;
    public function __construct(){
        $this->reader = new PDO('mysql:host=127.0.0.1;port=3306;dbname=CD;','root','password');
        $this->writer = new PDO('mysql:host=127.0.0.2;port=3306;dbname=CD;','root','password');
    }
    public function query($sql)
    {
        if (substr($sql, 0, 6) == 'select')
        {
            echo "讀操作: ".PHP_EOL;
            return $this->reader->query($sql);
        }
        else
        {
            echo "寫操作:".PHP_EOL;
            return  $this->writer->query($sql);
        }
    }
}
//資料庫代理
$proxy = new Proxy;
//讀操作
$proxy->query("select * from table");
//寫操作
$proxy->query("INSERT INTO table SET title = 'hello' where id = 1");


//當然對於資料庫來說,這裡應該使用單例模式的方法來存放$reader$writer,但我只是舉個例子,不想把單例加進來把程式碼搞複雜。
//但是如果你要實現這樣的一個資料庫代理,我覺得還是有必要用上單例模式的知識
複製程式碼

一句話來說,代理模式,就是在訪問物件時通過一個代理物件去訪問你想訪問的物件。而在代理物件中,我們可以實現對訪問物件的截斷或許可權控制等操作。


PHP設計模式(八)—外觀模式(Facade Pattern)

外觀模式 (Facade Pattern): 為子系統中的一組介面提供一個一致的介面,定義一個高層介面,這個介面使得這一子系統更加容易使用。

(一)為什麼需要外觀模式

1,開發階段,子系統越來越複雜,增加外觀模式提供一個簡單的呼叫介面。

2,維護一個大型遺留系統的時候,可能這個系統已經非常難以維護和擴充套件,但又包含非常重要的功能,為其開發一個外觀類,以便新系統與其互動。

3,外觀模式可以隱藏來自呼叫物件的複雜性。

(二)外觀模式UML圖

Facade Pattern

(三)簡單例項

比如說我們去醫院就診,醫院有醫生員工系統,有藥品系統,有患者資料系統。但是我們只是在前臺掛個號,就能在其他系統裡都看到我們。外觀系統就差不多這樣。

如果沒有掛號系統的話,我們就先要去醫生系統通知一下醫生, 然後去患者系統取一下患者資料交給醫生,再去藥品系統登記一下,最後到藥房領藥。

<?php
//醫院醫生員工系統
class DoctorSystem{

    //通知就診醫生
    static public function getDoctor($name){
        echo __CLASS__.":".$name."醫生,掛你號".PHP_EOL;
        return new Doctor($name);
    }
}
//醫生類
class Doctor{
    public $name;
    public function __construct($name){
        $this->name = $name;
    }
    public function prescribe($data){
        echo __CLASS__.":"."開個處方給你".PHP_EOL;
        return "祖傳祕方,藥到必死";
    }
}
//患者系統
class SufferSystem {
    static function getData($suffer){
        $data = $suffer."資料";
        echo  __CLASS__.":".$suffer."的資料是這些".PHP_EOL ;
        return  $data;
    }
}
//醫藥系統
class MedicineSystem {
    static function register($prescribe){
        echo __CLASS__.":"."拿到處方:".$prescribe."------------通知藥房發藥了".PHP_EOL;
        Shop::setMedicine("砒霜5千克");
    }
}
//藥房
class shop{
    static public $medicine;
    static function setMedicine($medicine){
        self::$medicine = $medicine;
    }
    static function getMedicine(){
        echo __CLASS__.":".self::$medicine.PHP_EOL;
    }
}

//如果沒有掛號系統,我們就診的第一步
//通知就診醫生
$doct = DoctorSystem::getDoctor("顧夕衣");
//患者系統拿病歷資料
$data = SufferSystem::getData("何在");
//醫生看病歷資料,開處方
$prscirbe = $doct->prescribe($data);
//醫藥系統登記處方
MedicineSystem::register($prscirbe);
//藥房拿藥
Shop::getMedicine();

echo PHP_EOL.PHP_EOL."--------有了掛號系統以後--------".PHP_EOL.PHP_EOL;

//掛號系統
class Facade{
    static public function regist($suffer,$doct){
        $doct = DoctorSystem::getDoctor($doct);
        //患者系統拿病歷資料
        $data = SufferSystem::getData($suffer);
        //醫生看病歷資料,開處方
        $prscirbe = $doct->prescribe($data);
        //醫藥系統登記處方
        MedicineSystem::register($prscirbe);
        //藥房拿藥
        Shop::getMedicine();
    }
}
//患者只需要掛一個號,其他的就讓掛號系統去做吧。
Facade::regist("葉好龍","賈中一");
複製程式碼

外觀模式,也叫門面模式。它多用於在多個子系統之間,作為中間層。使用者通過Facade物件,直接請求工作,省去了使用者呼叫多個子系統的複雜動作。

外觀模式常舉的一個例子,就是我們買了好多支股票,但是時間有限。盯盤很複雜,我們搞得一團糟。所以,我們乾脆買了股票基金。股票基金就好比於外觀模式的Facade物件,而子系統就是股票基金投的各支股票。


上一篇PHP建立型設計模式

感謝閱讀,由於筆者也是初學設計模式,能力有限,文章不可避免地有失偏頗 後續更新** PHP設計模式-結構型設計模式(下) **介紹,歡迎大家評論指正


我最近的學習總結:


歡迎大家關注我的微信公眾號 火風鼎

相關文章