PHP 設計模式(雜項)

fangle發表於2017-05-27

本文介紹PHP中常用的三個不屬於GOF 的設計模式

  • 資料對映模式(Data Mapper Pattern )
  • 注 冊 樹 模 式(Registry Pattern)
  • 空 對 象 模 式(Null Object Pattern)

PHP設計模式(二十三)—資料對映模式(Data Mapper Pattern)

資料對映模式(Data Mapper Pattern ):描述如何建立提供透明訪問任何資料來源的物件。資料對映模式,也叫資料訪問物件模式,或資料物件對映模式

(一)為什麼需要資料對映模式

資料對映模式的目的是讓持久化資料儲存層、駐於記憶體的資料表現層、以及資料對映本身三者相互獨立、互不依賴。這個資料訪問層由一個或多個對映器(或者資料訪問物件)組成,用於實現資料傳輸。通用的資料訪問層可以處理不同的實體型別,而專用的則處理一個或幾個。

(二)資料對映模式UML圖

PHP 設計模式(雜項)
Data Mapper Pattern

(三)簡單例項

通過資料物件對映模式,我們可以實現一個物件對應一條資料庫記錄,物件的屬性對應記錄的欄位。但物件的屬性改變時,自動更新資料庫記錄。

例如我們有一個使用者類與資料庫的使用者表對應

<?php
//資料模式對映類
class User
{
    protected $id;
    protected $data;
    protected $db;
    protected $change = false;

    public function __construct($id)
    {   
        $this->id = $id;
        //例項化資料庫物件,這裡使用了工廠方法
        $this->db = Factory::getDatabase();
        //從資料庫查詢資料,存放到data屬性中
        $this->data  = $this->db->query("select * from user where id = $id limit 1");

    }

    public function __get($key)
    {
        if (isset($this->data[$key]))
        {
            return $this->data[$key];
        }
    }

    public function __set($key, $value)
    {
        $this->data[$key] = $value;
        $this->change = true;
    }
    //析構方法
    public function __destruct()
    {
        //如果物件屬性改變過,則change屬性為true 則調更新方法更新資料庫
       $this->change && $this->update();
    }
    //更新記錄方法
    public function update(){
         foreach ($this->data as $k => $v)
            {
                $fields[] = "$k = '{$v}'";
            }
            $this->db->query("update user set " . implode(', ', $fields) . "where
            id = {$this->id} limit 1");
    }
}
//例項化物件
$user = new User(1);
//改變名字
$user->name = 'admin';複製程式碼

如果我們要實現實時更新,也可以不要change屬性,直接在__set方法中呼叫update方法,不用等到物件銷燬前再統一更新。當然實時更新時更新方法可以精簡地不需要foreach,只寫更新一個欄位指令就OK,但是這樣也帶來頻繁運算元據庫的問題。


PHP設計模式(二十四)—註冊樹模式(Registry Pattern)

註冊樹模式(Registry Pattern ):註冊樹模式為應用中經常使用的物件建立一箇中央儲存器來存放這些物件 —— 通常通過一個只包含靜態方法的抽象類來實現(或者通過單例模式)。也叫做註冊器模式

(一)為什麼需要註冊樹模式

解決常用物件的存放問題,實現類似於全域性變數的功能。

(二)註冊樹模式UML圖

暫無,跪求提供

(三)簡單例項

<?php
//User類用於測試
class User{}

//註冊樹類
class Registry
{
    protected static $objects;  //用於存放例項
    //存入例項方法
    static public function set($key, $object)
    {
        self::$objects[$key] = $object;
    }
    //獲取例項方法
    static public function get($key)
    {
        if (!isset(self::$objects[$key]))
        {
            return false;
        }
        return self::$objects[$key];
    }
    //刪除例項方法
    static public function _unset($key)
    {
        unset(self::$objects[$key]);
    }
}


$user = new User;
//存入例項
Registry::set('User',$user);
//檢視例項
var_dump(Registry::get('User'));
//刪除例項
Registry::_unset('User');
//再次檢視例項
var_dump(Registry::get('User'));複製程式碼

註冊樹經常與單例模式一起使用,先檢視註冊樹上是否有該例項,有就直接使用,沒有就生成一個例項,並掛到樹上。有些時候我們還可以這樣做,讓get方法如果get不到例項的時候就自動new一個存放起來,這樣我們使用時就不用管有沒有存放過這個例項,反正沒有的話get方法也會幫我們存放。

  //獲取例項方法
    static public function get($key)
    {
        if (!isset(self::$objects[$key]))
        {
            self::$objects[$key] = new $key;
        }
        return self::$objects[$key];
    }複製程式碼

當然使用這種方式的話,檢視例項是否存在,就不能使用get方法了。因為呼叫get方法以後,不存在也會生成一個例項。


PHP設計模式(二十五)—空物件模式(Null Object Pattern)

空物件模式(Null Object Pattern):用一個空物件取代 NULL,減少對例項的檢查。這樣的空物件可以在資料不可用的時候提供預設的行為

(一)為什麼需要空物件模式

解決在需要一個物件時返回一個null值,使其呼叫函式出錯的情況

(二)空物件模式UML圖

PHP 設計模式(雜項)
Null Object Pattern

上圖是Java的空物件模式UML圖,網上很多PHP設計模式的程式碼實現都是照著上面這個UML圖

實際上PHP在空物件模式的實現上比Java更加簡單些。因為PHP有美妙的語法糖,魔術方法__call方法。

我們只要實現空物件的__call方法就可以實現空物件模式,並不需要使用nullobject去繼承對應的抽象object

(三)簡單例項

假設現在我們有這麼一段程式碼

<?php
//測試類
class Person{
    public function code(){
        echo 'code makes me happy'.PHP_EOL;
    }
}


//定義一個生成物件函式,只有PHPer才允許生成物件
function getPerson($name){
    if($name=='PHPer'){
        return new Person;
    }
}

$phper = getPerson('PHPer');
$phper->code();複製程式碼

是的,現在這段程式碼會輸出code makes me happy。如果有時候這個函式是別人呼叫的,它並沒傳入合適的引數呢?

$phper = getPerson('Javaer');
$phper->code();複製程式碼

這個時候就會報錯了error : Call to a member function code() on null。是的$phper現在是一個null值,所以呼叫code方法就會報錯

這種情況很常見,系統並沒有返回一個我們期待的物件,而是返回了一個null值。所以多數情況下,我們的程式碼都要這樣寫

$phper = getPerson('Javaer');
if(!is_null($phper)){
    $phper->code();
}複製程式碼

或者是

if(is_object($phper)){
    $phper->code();
}複製程式碼

這樣讓太多的if判斷不可避免地存在於我們的程式碼中
如果我們使用NullObject模式的話,我們就可以讓函式沒有返回值時返回一個nullobject物件。而不是一個null值(沒有return 預設null值

//測試類
class Person{
    public function code(){
        echo 'code makes me happy'.PHP_EOL;
    }
}


//空物件模式
class NullObject{}

//定義一個生成物件函式,只有PHPer才允許生成物件
function getPerson($name){
    if($name=='PHPer'){
        return new Person;
    }else{
        return new NullObject;
    }
}

$phper = getPerson('PHer');
$phper->code();複製程式碼

這個時候就不會再報一個null呼叫函式的錯誤了,但是會報一個call to undefined method的錯誤,這是由於NullObject物件沒有code方法。這個時候我們只需實現魔術方法__call,就不會報錯了。

//空物件模式
class NullObject{
    public  function __call($method,$arg){
        echo 'this is NullObject';
    }
}複製程式碼

我們可以通過返回一個NullObject物件來取代返回null,這樣就不用在呼叫方法時判斷是否為null,而且只要你實現了__call方法,不管真正的物件它原來是呼叫那個方法的,NullObject都可以去呼叫而且不報錯(實際是隱式呼叫了魔術方法__call)。當然,如果你原本的邏輯是返回物件是null的話什麼都不做,那麼你可以讓__call()什麼都不做。或者你也可以讓它丟擲一個異常。


上一篇::PHP行為型設計模式(四)

感謝閱讀,由於筆者也是初學設計模式,能力有限,文章不可避免地有失偏頗
還請大家批評指正


我最近的學習總結:


PHP 設計模式(雜項)
歡迎大家關注我的微信公眾號 火風鼎

相關文章