PHP開發自己的框架,你必須知道這些知識點!

2020-07-16 10:06:01

一、PHP常用的四種資料結構

簡介:spl是php的一個標準庫。

官方文件:http://php.net/manual/zh/book.spl.php

<?php

//spl(php標準庫)資料結構

/**
 * 棧(先進後出)
 */
$stack = new SplStack();
$stack->push('data1');//入棧(先進後出)
$stack->push('data2');//入棧
$stack->push('data3');//入棧

echo $stack->pop();//出棧
echo $stack->pop();//出棧
echo $stack->pop();//出棧


/**
 *佇列(先進先出)
 */
$queue = new SplQueue();
$queue->enqueue('data4');//入佇列
$queue->enqueue('data5');//入佇列
$queue->enqueue('data6');//入佇列

echo $queue->dequeue();//出佇列
echo $queue->dequeue();//出佇列
echo $queue->dequeue();//出佇列
echo $queue->dequeue();//出佇列


/**
 * 堆
 */
$heap = new SplMinHeap();
$heap->insert('data8');//入堆
$heap->insert('data9');//入堆
$heap->insert('data10');//入堆

echo $heap->extract();//從堆中提取資料
echo $heap->extract();//從堆中提取資料
echo $heap->extract();//從堆中提取資料


/**
 * 固定陣列(不論使不使用,都會分配相應的記憶體空間)
 */
$array = new SplFixedArray(15);
$array['0'] = 54;
$array['6'] = 69;
$array['10'] = 32;
var_dump($array);

二、PHP鏈式操作的實現(原理)

1、入口檔案 index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入


$db = new ExtendDatabase();
$db->where('uid < 100000')->->order('uid desc')->limit(100);

2、自動載入類 Loader.php

<?php
namespace Extend;
/**
 * 實現框架的自動載入
 */
class Loader
{
    /**
     * 實現檔案的自動載入
     */
    static function autoload($class)
    {
        require BASEDIR.'/'.str_replace('','/',$class).'.php';
    }


}

3、資料庫類Database.php

註:只是原理,並沒有對方法進行具體的封裝,具體的封裝還是看個人喜好去定鏈式查詢的風格。

<?php
namespace Extend;

class Database
{
    /**
     * 指定查詢條件
     * @param $where
     */
    function where($where)
    {
        return $this;
    }

    /**
     * 指定排序條件
     */
    function order($order)
    {
        return $this;
    }

    /**
     * 指定查詢的限制條數
     * @param $limit
     */
    function limit($limit)
    {
        return $this;
    }


}

其實就是對傳過來的條件進行重新的底層封裝,然後再把當前物件返回,使得可以不斷的鏈式查詢。

三、PHP魔術方法的使用

在php設計模式中,會涉及到很多魔術方法的使用,這裡也對經常會用到的魔術方法進行簡單總結。

1、框架入口檔案 index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入


/**
 * 魔術方法的使用
 */

# 範例化Object類
$obj = new ExtendObject();//當前檔案不存在這個類,就會自動執行自動載入函數去包含相應的類檔案(即 Extend/Object.php)


# __set 和 __get 對不存在的屬性進行接管
$obj->title = 'xiaobudiu'; //當對一個不存在的類屬性賦值時,會自動呼叫類中定義的__set()
echo $obj->title; //當呼叫一個不存在的類屬性時,會自動呼叫類中定義的__get()


# __call 和 __callStatic 對不存在或者許可權不夠的類方法進行接管
$obj->getUserInfo('1000068'); //當呼叫一個不存在的類方法時,會呼叫__call(),並自動將當前方法名和引數傳到__call方法中
ExtendObject::getOpenId('1000068'); //當呼叫一個不存在的類靜態方法時,會呼叫__callStatic(),並自動將當前方法名和引數傳遞到__callStatic方法中


# echo或print物件時,由__toString 接管
echo $obj; //當echo或print一個物件時,會自動呼叫類中定義的__toString方法


# 在php中,如果我們把一個物件當成函數用,則由__invoke()接管
$obj('xiaobudiu');//當我們將一個物件當成函數用的時候,會自動呼叫當前類中定義的__invoke()方法

2、 Extend/Object.php

<?php
namespace Extend;
/**
 * 要求類名必須和檔名保持一致,即類名是Object,則所在檔名為Object.php
 * Class Object
 * @package Extend
 */
class Object
{
    protected $array = array();

    /**
     * 在程式碼要給未定義的屬性賦值時呼叫,或在類外部修改被private修飾的類屬性時被呼叫
     */
    function __set($name, $value)
    {
        echo "this is __set func";
    }

    /**
     * 當在類外部存取被private或proteced修飾的屬性或存取一個類中原本不存在的屬性時被呼叫
     * @param $name
     */
    function __get($name)
    {
        echo "this is __get func";
    }

    /**
     * 當試圖呼叫不存在的方法或許可權不足時會觸發__call()
     * @param $name 呼叫不存在的類方法時那個不存在的類方法的方法名
     * @param $arguments 呼叫不存在的類方法時傳遞的引數
     */
    function __call($name, $arguments)
    {
        var_dump($name,$arguments);
    }


    /**
     * 當試圖呼叫不存在的靜態方法或許可權不足時會觸發__callStatic()
     * @param $name 呼叫不存在的靜態方法時那個不存在的方法的方法名
     * @param $arguments 呼叫不存在的靜態方法時傳遞的引數
     */
    function __callStatic($name,$arguments)
    {
       var_dump($name,$arguments);
    }

    /**
     * 當使用echo或print列印物件時會呼叫__toString()方法將物件轉化為字串
     */
    function __toString()
    {
        echo "this is __toString func";
    }


    /**
     * 物件本身不能直接當函數用,如果被當做函數用,會直接回撥__invoke方法
     * @param $param
     */
    function __invoke($param)
    {
        echo $param."<br>this is __invoke func";
    }


}

四、三種基礎設計模式

1、工廠模式

通過傳入引數的不同,來範例化不同的類。

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

//構造範例化快取類時傳入的引數
$config = array(
    'host' => '127.0.0.1',
    'pass' => 'myRedis&&&'
);
//工廠模式建立cache物件
$cache = ExtendCacheFactory::getCacheObj('redis',$config);
var_dump($cache);

Extend/CacheFactory.php

<?php
namespace Extend;

class CacheFactory
{
    const FILE = 1;
    const MEMCACHE = 2;
    const REDIS = 3;

    static $instance;//定義靜態屬性,用於儲存物件

    /**
     * 工廠類建立快取物件
     * @param $type 指定快取型別
     * @param array $options 傳入快取引數
     * @return FileCache|Memcache|RedisCache
     */
    static function getCacheObj($type, array $options)
    {
        switch ($type) {
            case 'file':
            case self::FILE:
                self::$instance = new FileCache($options);
                break;

            case 'memcache':
            case self::MEMCACHE:
                self::$instance = new Memcache($options);
                break;

            case 'redis':
            case self::REDIS:
                self::$instance = new RedisCache($options);
                break;

            default:
                self::$instance = new FileCache($options);
                break;

        }
        return self::$instance;
    }
}

2、單例模式

保證一個類只範例化一個類物件,進而減少系統開銷和資源的浪費

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

//單例模式建立物件
$obj = ExtendSingleObject::getInstance();
$obj2 = ExtendSingleObject::getInstance();
var_dump($obj,$obj2);//從結果可以看出,兩個範例化的物件其實是一個物件

Extend/SingleObject.php

<?php
namespace Extend;

/**
 * 單例模式建立唯一類物件
 * Class SingleObject
 * @package Extend
 */
class SingleObject
{
    //私有的靜態屬性,用於儲存類物件
    private static $instance = null;

    //私有的構造方法,保證不允許在類外 new
    private function __construct(){}

    //私有的克隆方法, 確保不允許通過在類外 clone 來建立新物件
    private function __clone(){}

    //公有的靜態方法,用來範例化唯一當前類物件
    public static function getInstance()
    {
        if(is_null(self::$instance)){
            self::$instance = new self;
        }
        return self::$instance;
    }

}

3、註冊樹模式

將我們用到的物件註冊到註冊樹上,然後在之後要用到這個物件的時候,直接從註冊樹上取下來就好。(就和我們用全域性變數一樣方便)

Extend/RegisterTree,php

<?php
namespace Extend;

/**
 * 註冊樹模式
 * Class RegisterTree
 * @package Extend
 */
class RegisterTree
{

    static protected $objects;//靜態類屬性,用於儲存註冊到註冊樹上的物件

    /**
     * 將物件註冊到註冊樹上
     * @param $alias 物件的別名
     * @param $object 物件
     */
    static function setObject($alias,$object)
    {
        self::$objects[$alias] = $object;
    }


    /**
     * 從註冊樹上取出給定別名相應的物件
     * @param $alias 將物件插入到註冊樹上時寫的別名
     * @return mixed 物件
     */
    static protected function getObject($alias)
    {
        return self::$objects[$alias];
    }

    /**
     * 將物件從註冊樹上刪除
     * @param $alias 將物件插入到註冊樹上時寫的別名
     */
    public function unsetObject($alias)
    {
        unset(self::$objects[$alias]);
    }

}

五、其他常見的8種PHP設計模式

1、介面卡模式

將一個類的介面轉換成客戶希望的另一個介面,介面卡模式使得原本的由於介面不相容而不能一起工作的那些類可以一起工作。
應用場景:老程式碼介面不適應新的介面需求,或者程式碼很多很亂不便於繼續修改,或者使用第三方類庫。

常見的有兩種介面卡,分別是類介面卡和物件介面卡,這裏拿更看好的物件介面卡舉例:

<?php
namespace Extend;

/**
 * 物件介面卡模式具體流程
 * 1、根據需求定義介面,進而滿足新需求功能
 * 2、定義新類,繼承並實現定義的介面
 * 3、在實現介面時,原有的功能,只通過原有類物件呼叫原有類功能(委託)
 * 4、再根據需求,在新類中實現新需求功能
 * 【適用性】
 * (1)你想使用一個已經存在的類,而它的介面不符合你的需求
 * (2)你想建立一個可以複用的類,該類可以與其他不相關的類或不可預見的類協同工作
 * (3)你想使用一個已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的介面。物件介面卡可以適配它的父類別介面(僅限於對
 */


/**
 * 目標角色(根據需求定義含有舊功能加上新功能的介面)
 * Interface Target 我們期望得到的功能類
 * @package Extend
 */
interface Target
{
    public function simpleMethod1();
    public function simpleMethod2();
}

/**
 * 源角色(在新功能提出之前的舊功能類和方法)
 * Class Adaptee
 * @package Extend
 */
class Adaptee
{

    public function simpleMethod1()
    {
        echo 'Adapter simpleMethod1'."<br>";
    }

}

/**
 * 類介面卡角色(新定義介面的具體實現)
 * Class Adapter
 * @package Extend
 */
class Adapter implements Target
{

    private $adaptee;

    function __construct()
    {
        //介面卡初始化直接new 原功能類,以方便之後委派
        $adaptee = new Adaptee();
        $this->adaptee = $adaptee;
    }

    //委派呼叫Adaptee的sampleMethod1方法
    public function simpleMethod1()
    {
        echo $this->adaptee->simpleMethod1();
    }

    public function simpleMethod2()
    {
        echo 'Adapter simpleMethod2'."<br>";
    }

}

/**
 * 用戶端呼叫
 */
$adapter = new Adapter();
$adapter->simpleMethod1();
$adapter->simpleMethod2();

2、策略模式

將一組特定的行為和演算法封裝成類,以適應某些特定的上下文環境,這種模式就是策略模式,策略模式可以實現依賴倒置以及控制反轉。

範例舉例:假如一個電商網站系統,針對男性女性使用者要各自跳轉到不同的商品類目,並且所有的廣告位展示展示不同的廣告。

index.php

<?php

/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

/**
 * 首頁資料控制器
 * Class Index
 */
class Home
{
    /**
     * 最好寫上這個注釋,告訴phpstorm是對應的哪個介面類,否則雖然程式執行正確,但phpstorm識別不了
     * @var ExtendUserType
     */
    protected $userType;

    /**
     * 首頁展示資料
     * 使用策略模式
     * Index constructor.
     */
    function index()
    {
        echo "AD:";
        $this->userType->showAd();
        echo "Category:";
        $this->userType->showCategory();
    }

    /**
     * 策略模式
     * 根據傳遞的使用者性別展示不同類別資料
     * @param ExtendUserType $userType
     */
    function setUserType(ExtendUserType $userType)
    {
        $this->userType = $userType;
    }

}

$obj = new Home();
if ($_GET['userType'] == 'female'){
    $userType = new ExtendFemaleUserType();
} else {
    $userType = new ExtendMaleUserType();
}
$obj->setUserType($userType);
$obj->index();

Extend/userType.php(定義的介面)

<?php

namespace Extend;

/**
 * 策略模式
 * 定義根據性別不同展示不同商品類目和廣告介面
 * Interface UserType
 * @package Extend
 */
interface UserType
{
    //顯示廣告
    function showAd();
    //展示類目
    function showCategory();

}

MaleUserType.php、FemaleUserType.php(具體實現的類 )

<?php

namespace Extend;

/**
 * 定義男性商品類目和廣告位資料介面
 * Class MaleUserType
 * @package Extend
 */
class MaleUserType implements UserType
{
    /**
     * 廣告欄資料展示
     */
    function showAd()
    {
        echo "this is 男性’s 廣告條目資料";
    }

    /**
     * 商品類目資料展示
     */
    function showCategory()
    {
        echo "this is 男性’s 商品類目資料";
    }

}
<?php

namespace Extend;

/**
 * 定義女性商品類目和廣告位資料介面
 * Class FemaleUserType
 * @package Extend
 */
class FemaleUserType implements UserType
{

    /**
     * 廣告欄資料展示
     */
    function showAd()
    {
        echo "this is 女性’s 廣告條目資料";
    }

    /**
     * 商品類目資料展示
     */
    function showCategory()
    {
        echo "this is 女性’s 商品類目資料";
    }


}

顯示效果:

3、資料物件對映模式

將物件和資料儲存對映起來,對一個物件的操作會對映為對資料儲存的操作。

下面在程式碼中實現資料物件對映模式,我們將實現一個ORM類,將複雜的sql語句對映成物件屬性的操作。並結合使用資料物件對映模式、工廠模式、註冊模式。

-----(1)資料庫對映模式簡單範例實現

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

//使用資料物件對映模式代替寫sql
$user = new ExtendUser(25);
$user->name = '小蔔丟飯糰子';
$user->salary = '20000';
$user->city = '浙江省';

Extend/User.php

<?php

namespace Extend;

class User
{
    //對應資料庫中的4個欄位
    public $id;
    public $name;
    public $salary;
    public $city;
    //儲存資料庫連線物件屬性
    protected $pdo;

    public $data;

    function __construct($id)
    {
        $this->id = $id;
        $this->pdo = new PDO('mysql:host=127.0.0.1;dbname=test','root','123456');
    }

    function __destruct()
    {
        $this->pdo->query("update user set name = '{$this->name}',salary = '{$this->salary}',city = '{$this->city}' where id='{$this->id}'");
    }
}

這樣,執行index.php檔案,資料庫就會發生相應的操作,也就實現了基本的資料物件對映。

-------(2)資料庫對映模式複雜案例實現

index.php

<?php

/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入


class EX
{
    function index()
    {
        //使用資料物件對映模式代替寫sql
        $user = ExtendFactory::getUserObj(25);
        $user->name = '小蔔丟飯糰子';
        $user->salary = '20000';
        $user->city = '浙江省';
    }

    function test()
    {
        $user = ExtendFactory::getUserObj(25);
        $user->city = '廣東省';
    }

}

$ex = new EX();
$ex->index();

Extend/Factory.php

<?php

namespace Extend;

class Factory
{
    /**
     * 工廠模式建立資料庫物件,單例模式保證建立唯一db物件
     * @return mixed
     */
    static function CreateDatabaseObj()
    {
        $db = Database::getInstance();
        return $db;
    }

    /**
     * 工廠模式建立user物件,註冊樹模式保證建立唯一物件,避免資源浪費
     * @param $id
     * @return User|mixed
     */
    static function getUserObj($id)
    {
        $key = 'user'.$id;
        $user = RegisterTree::getObject($key);
        if (!$user) {
            $user = new User($id);
            RegisterTree::setObject($key,$user);
        }
        return $user;
    }
}

Extend/Register.php

<?php

namespace Extend;

/**
 * 註冊樹模式
 * Class RegisterTree
 * @package Extend
 */
class RegisterTree
{
    static protected $objects;//靜態類屬性,用於儲存註冊到註冊樹上的物件

    /**
     * 將物件註冊到註冊樹上
     * @param $alias 物件的別名
     * @param $object 物件
     */
    static function setObject($alias,$object)
    {
        self::$objects[$alias] = $object;
    }


    /**
     * 從註冊樹上取出給定別名相應的物件
     * @param $alias 將物件插入到註冊樹上時寫的別名
     * @return mixed 物件
     */
    static function getObject($alias)
    {
        return self::$objects[$alias];
    }

    /**
     * 將物件從註冊樹上刪除
     * @param $alias 將物件插入到註冊樹上時寫的別名
     */
    public function unsetObject($alias)
    {
        unset(self::$objects[$alias]);
    }

}

Extend/User.php

<?php

namespace Extend;

class User
{
    //對應資料庫中的4個欄位
    public $id;
    public $name;
    public $salary;
    public $city;
    //儲存資料庫連線物件屬性
    protected $pdo;

    public $data;

    function __construct($id)
    {
        $this->id = $id;
        $this->pdo = new PDO('mysql:host=127.0.0.1;dbname=test','root','123456');
    }

    function __destruct()
    {
        $this->pdo->query("update user set name = '{$this->name}',salary = '{$this->salary}',city = '{$this->city}' where id='{$this->id}'");
    }
}

這樣,就實現了稍複雜的資料物件對映模式和工廠模式、註冊樹模式相結合的案例。

4、觀察者模式

當一個物件狀態發生改變時,依賴它的物件會全部收到通知,並自動更新。

場景:一個事件發生後,要執行一連串更新操作。傳統的程式設計方式就是在事件的程式碼之後直接加入處理邏輯,當更新的邏輯增多之後,程式碼會變的難以維護。這種方式是耦合的,侵入式的,增加新的邏輯需要修改事件主體的程式碼。觀察者模式實現了低耦合,非侵入式的通知與更新機制。

4.1、傳統模式舉例:

<?php

/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入


/**
 * 一個事件的邏輯控制器
 * Class Event
 */
class Event
{
    /**
     * 使用者確認訂單
     */
    function firmOrder()
    {
        //這裡假設一個事件發生了,比如使用者已經完成下單
        echo "使用者已下單<br>";
        //傳統方式是在發生一個事件之後直接進行一系列的相關處理,耦合度比較高,比如寫入紀錄檔,給使用者發郵件等等
        echo "在使用者下單之後進行的一系列操作<br>";
    }

}

$event = new Event();
$event->firmOrder();

4.2、觀察者模式典型實現方式:

(1)定義2個介面:觀察者(通知)介面、被觀察者(主題)介面

(2)定義2個類,觀察者類實現觀察者介面、被觀察者類實現被觀察者介面

(3)被觀察者註冊自己需要通知的觀察者

(4)被觀察者類某個業務邏輯發生時,通知觀察者物件,進而每個觀察者執行自己的業務邏輯。

程式碼範例:

test.php

<?php
/**
 * 觀察者模式場景描述:
 * 1、購票後記錄文字紀錄檔
 * 2、購票後記錄資料庫紀錄檔
 * 3、購票後傳送簡訊
 * 4、購票送抵扣卷、兌換卷、積分
 * 5、其他各類活動等
 */


/**
 * 觀察者介面
 */
interface TicketObserver
{
    function buyTicketOver($sender, $args); //得到通知後呼叫的方法
}

/**
 * 被觀察者介面(購票主題介面)
 */
interface TicketObserved
{
    function addObserver($observer); //提供註冊觀察者方法
}


/**
 * 主體邏輯,繼承被觀察者介面
 * Class BuyTicket
 */
class BuyTicket implements TicketObserved
{

    /**
     * 定義觀察者陣列屬性,用於儲存觀察者
     * @var array
     */
    private $observers = array();


    /**
     * 實現被觀察者介面定義的方法(新增觀察者)
     * @param $observer 範例化後的觀察者物件
     */
    public function addObserver($observer)
    {
        $this->observers[] = $observer;
    }


    /**
     * 購票主體方法
     * BuyTicket constructor.
     * @param $ticket 購票排號
     */
    public function buyTicket($ticket)
    {
        //1、根據需求寫購票邏輯
        //..............

        //2、購票成功之後,迴圈通知觀察者,並呼叫其buyTicketOver實現不同業務邏輯
        foreach ($this->observers as $observe) {
            $observe->buyTicketOver($this, $ticket); //$this 可用來獲取主題類控制代碼,在通知中使用
        }

    }

}



/**
 * 購票成功後,傳送簡訊通知
 * Class buyTicketMSN
 */
class buyTicketMSN implements TicketObserver
{
    public function buyTicketOver($sender, $ticket)
    {
        echo (date ( 'Y-m-d H:i:s' ) . " 簡訊紀錄檔記錄:購票成功:$ticket<br>");
    }
}

/**
 * 購票成功後,記錄紀錄檔
 * Class buyTicketLog
 */
class buyTicketLog implements TicketObserver
{
    public function buyTicketOver($sender, $ticket) 
    {
        echo (date ( 'Y-m-d H:i:s' ) . " 文字紀錄檔記錄:購票成功:$ticket<br>");
    }
}


/**
 * 購票成功後,贈送優惠券
 * Class buyTicketCoupon
 */
class buyTicketCoupon implements TicketObserver
{
    public function buyTicketOver($sender, $ticket) 
    {
        echo (date ( 'Y-m-d H:i:s' ) . " 贈送優惠券:購票成功:$ticket 贈送10元優惠券1張。<br>");
    }
}


//範例化購票類
$buy = new BuyTicket();
//新增多個觀察者
$buy->addObserver(new buyTicketMSN());
$buy->addObserver(new buyTicketLog());
$buy->addObserver(new buyTicketCoupon());
//開始購票
$buy->buyTicket ("7排8號");

瀏覽器顯示結果:

5、原型模式

原型模式與工廠模式的作用類似,都是用來建立物件的。但是實現方式是不同的。原型模式是先建立好一個原型物件,然後通過clone原型物件來建立新的物件。這樣,就免去了類建立時重複的初始化操作。

原型模式適用於大物件的建立,建立一個大物件需要很大的開銷,如果每次new就會消耗很大,原型模式僅需記憶體拷貝即可。

程式碼範例:

<?php
/**
 * 抽象原型角色
 */
interface Prototype
{
    public function copy();
}

/**
 * 具體原型角色
 */
class ConcretePrototype implements Prototype
{

    private $_name;

    public function __construct($name)
    {
        $this->_name = $name;
    }

    public function setName($name)
    {
        $this->_name = $name;
    }

    public function getName()
    {
        return $this->_name;
    }

    public function copy()
    {
        //深拷貝實現
         //$serialize_obj = serialize($this); // 序列化
         //$clone_obj = unserialize($serialize_obj); // 反序列化
         //return $clone_obj;

        // 淺拷貝實現
        return clone $this;
    }

}

/**
 * 測試深拷貝用的參照類
 */
class Demo
{
    public $array;
}


//測試
$demo = new Demo();
$demo->array = array(1, 2);
$object1 = new ConcretePrototype($demo);
$object2 = $object1->copy();

var_dump($object1->getName());
echo '<br />';
var_dump($object2->getName());
echo '<br />';

$demo->array = array(3, 4);
var_dump($object1->getName());
echo '<br />';
var_dump($object2->getName());
echo '<br />';

瀏覽器顯示結果:

6、裝飾器模式

可以動態的新增或修改類的功能

一個類實現一個功能,如果要再修改或新增額外的功能,傳統的程式設計模式需要寫一個子類繼承它,並重新實現類的方法。

使用裝飾器模式,僅需在執行時新增一個裝飾器物件即可實現,可以實現最大的靈活性。

<?php
/**
 * 裝飾器流程
 * 1、宣告裝飾器介面(裝飾器介面)
 * 2、具體類繼承並實現裝飾器介面(顏色裝飾器實現,字型大小裝飾器實現)
 * 3、在被裝飾者類中定義"新增裝飾器"方法(EchoText類中的addDecorator方法)
 * 4、在被裝飾者類中定義呼叫裝飾器的方法(EchoText類中的beforeEcho和afterEcho方法)
 * 5、使用時,範例化被裝飾者類,並傳入裝飾器物件(比如new ColorDecorator('yellow'))
 */

/**
 * 裝飾器介面
 * Class Decorator
 */
interface Decorator
{
    public function beforeEcho();
    public function afterEcho();
}

/**
 * 顏色裝飾器實現
 * Class ColorDecorator
 */
class ColorDecorator implements Decorator
{
    protected $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    public function beforeEcho()
    {
        echo "<dis style='color: {$this->color}'>";
    }

    public function afterEcho()
    {
        echo "</p>";
    }
}

/**
 * 字型大小裝飾器實現
 * Class SizeDecorator
 */
class SizeDecorator implements Decorator
{
    protected $size;

    public function __construct($size)
    {
        $this->size = $size;
    }

    public function beforeEcho()
    {
        echo "<dis style='font-size: {$this->size}px'>";
    }

    public function afterEcho()
    {
        echo "</p>";
    }
}

/**
 * 被裝飾者
 * 輸出一個字串
 * 裝飾器動態新增功能
 * Class EchoText
 */
class EchoText
{
    protected $decorators = array();//存放裝飾器

    //裝飾方法
    public function Index()
    {
        //呼叫裝飾器前置操作
        $this->beforeEcho();
        echo "你好,我是裝飾器。";
        //呼叫裝飾器後置操作
        $this->afterEcho();
    }

    //新增裝飾器
    public function addDecorator(Decorator $decorator)
    {
        $this->decorators[] = $decorator;
    }

    //執行裝飾器前置操作 先進先出原則
    protected function beforeEcho()
    {
        foreach ($this->decorators as $decorator)
            $decorator->beforeEcho();
    }

    //執行裝飾器後置操作 先進後出原則
    protected function afterEcho()
    {
        $tmp = array_reverse($this->decorators);
        foreach ($tmp as $decorator)
            $decorator->afterEcho();
    }
}

//範例化輸出類
$echo = new EchoText();
//增加裝飾器
$echo->addDecorator(new ColorDecorator('yellow'));
//增加裝飾器
$echo->addDecorator(new SizeDecorator('22'));
//輸出
$echo->Index();

7、疊代器模式

在不需要了解內部實現的前提下,遍歷一個聚合物件的內部元素而又不暴露該物件的內部表示,這就是PHP疊代器模式的定義。

相對於傳統程式設計模式,疊代器模式可以隱藏遍歷元素的所需的操作。

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

$users = new ExtendAllUser();
//迴圈遍歷出所有使用者資料
foreach ($users as $user) {
    var_dump($user);
}

Extend/AllUser.php

<?php
namespace Extend;

/**
 * 疊代器模式,繼承php內部自帶的疊代器介面(Iterator)
 * Class AllUser
 * @package Extend
 */
class AllUser implements Iterator
{
    protected $index = 0;//表示索引
    protected $ids = array();//用於儲存所有user的id(實際應用中,可以採用註冊樹模式進行儲存)
    protected $pdo;//用於儲存資料庫物件

    function __construct()
    {
        //獲取pdo資料庫物件
        $this->pdo = new PDO('mysql:host=127.0.0.1;dbname=test','root','123456');
        //獲取所有使用者的id
        $this->ids = $this->pdo->query("select id from user")->fetchAll(2);
    }

    /**
     * 實現介面方法,重置疊代器,回到集合開頭
     */
    public function rewind()
    {
        $this->index = 0;
    }

    /**
     * 實現介面方法,獲取當前元素
     * @return mixed|void
     */
    public function current()
    {
        $id = $this->ids[$this->index]['id'];
        //獲取當前使用者的資料
        $user_data = $this->pdo->query("select * from user where id='{$id}'")->fetch(2);
        return $user_data;
    }

    /**
     * 實現介面方法,獲取當前元素鍵值
     * @return mixed|void
     */
    public function key()
    {
        return $this->index;
    }

    /**
     * 實現介面方法,獲取下一個元素
     */
    public function next()
    {
        $this->index++;
    }

    /**
     * 實現介面方法,驗證是否還有下一個元素
     * @return bool|void
     */
    public function valid()
    {
        return $this->index < count($this->ids);
    }

}

8、代理模式

在用戶端與實體之間建立一個代理物件(proxy),用戶端對實體進行操作全部委派給代理物件,隱藏實體的具體實現細節。

典型的應用就是mysql的主從結構,讀寫分離。在mysql中,對所有讀的操作請求從庫,所有寫的操作請求主庫。

宣告一個代理類,前台使用時只需建立一個代理類,呼叫對應方法即可。程式碼範例:

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

// 1、傳統程式設計模式是手動選擇
#查詢操作使用從庫
//$db_slave = ExtendFactory::getDatabase('slave');
//$info = $db_slave->query("select * from user where id = 1 limit 1");
#增刪改操作使用主庫
//$db_master = ExtendFactory::getDatabase('master');
//$db_master->query("update user name = 'xiaobudiu' where id = 29 limit 1");


// 2、使用代理模式
$db_proxy = new ExtendProxy();
$db_proxy->getUserName(1);
$db_proxy->setUserName(29,'xiaobudiu');

Extend/Proxy.php

<?php
namespace Extend;

class Proxy implements IUserProxy
{
    function getUserName($id)
    {
        $db = Factory::getDatabase('slave');
        $db->query("select name from user where id =$id limit 1");
    }

    function setUserName($id, $name)
    {
        $db = Factory::getDatabase('master');
        $db->query("update user set name = $name where id =$id limit 1");
    }
}

Extend/Factory.php

<?php
namespace Extend;

class Factory
{
    static function getDatabase($id)
    {
        $key = 'database_'.$id;
        if ($id == 'slave')
        {
            $slaves = Application::getInstance()->config['database']['slave'];
            $db_conf = $slaves[array_rand($slaves)];
        } else {
            $db_conf = Application::getInstance()->config['database'][$id];
        }
        //註冊樹模式儲存及獲取物件
        $db = Register::get($key);
        if (!$db) {
            $db = new DatabaseMySQLi();
            $db->connect($db_conf['host'], $db_conf['user'], $db_conf['password'], $db_conf['dbname']);
            Register::set($key, $db);
        }
        return $db;
    }

}

Extend/Application.php

<?php
namespace Extend;

class Application
{
    public $base_dir;
    protected static $instance;

    public $config;

    protected function __construct($base_dir)
    {
        $this->base_dir = $base_dir;
        $this->config = new Config($base_dir.'/configs');
    }

    static function getInstance($base_dir = '')
    {
        if (empty(self::$instance))
        {
            self::$instance = new self($base_dir);
        }
        return self::$instance;
    }
    
}

Extend/Config.php

<?php
namespace Extend;

/**
 * 設定類,繼承於php自帶的ArrayAccess介面
 * 允許一個物件以陣列的方式存取
 * Class Config
 * @package Extend
 */
class Config implements ArrayAccess
{
    protected $path;
    protected $configs = array();

    function __construct($path)
    {
        $this->path = $path;
    }

    function offsetGet($key)
    {
        if (empty($this->configs[$key]))
        {
            $file_path = $this->path.'/'.$key.'.php';
            $config = require $file_path;
            $this->configs[$key] = $config;
        }
        return $this->configs[$key];
    }

    function offsetSet($key, $value)
    {
        throw new Exception("cannot write config file.");
    }

    function offsetExists($key)
    {
        return isset($this->configs[$key]);
    }

    function offsetUnset($key)
    {
        unset($this->configs[$key]);
    }
}

configs/database.php

<?php
$config = array(
    'master' => array(
        'type' => 'MySQL',
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => '123456',
        'dbname' => 'test',
    ),
    'slave' => array(
        'slave1' => array(
            'type' => 'MySQL',
            'host' => '127.0.0.1',
            'user' => 'root',
            'password' => '123456',
            'dbname' => 'test',
        ),
        'slave2' => array(
            'type' => 'MySQL',
            'host' => '127.0.0.1',
            'user' => 'root',
            'password' => '123456',
            'dbname' => 'test',
        ),
    ),
);
return $config;

五、其餘設計模式以及總結

六、物件導向程式設計的基本原則

1、單一職責原則:一個類只需要做好一件事情。不要使用一個類完成很多功能,而應該拆分成更多更小的類。

2、開放封閉原則:一個類寫好之後,應該是可延伸而不可修改的。

3、依賴倒置原則:一個類不應該強依賴另外一個類,每個類對於另外一個類都是可替換的。

4、設定化原則:盡量使用設定,而不是寫死。

5、面向介面程式設計原則:只需要關心某個類提供了哪些介面,而不需要關心他的實現。

七、自動載入設定類檔案

1、php中使用ArrayAccess實現組態檔的載入(使得程式可以以陣列的方式進行讀取設定)

(1)定義Config.php,繼承php自帶的ArrayAccess介面,並實現相應的方法,用於讀取和設定設定

Extend/Config.php

<?php
namespace Extend;

/**
 * 設定類,繼承於php自帶的ArrayAccess介面
 * 允許一個物件以陣列的方式存取
 * Class Config
 * @package Extend
 */
class Config implements ArrayAccess
{
    protected $path;
    protected $configs = array();

    function __construct($path)
    {
        $this->path = $path;
    }

    function offsetGet($key)
    {
        if (empty($this->configs[$key]))
        {
            $file_path = $this->path.'/'.$key.'.php';
            $config = require $file_path;
            $this->configs[$key] = $config;
        }
        return $this->configs[$key];
    }

    function offsetSet($key, $value)
    {
        throw new Exception("cannot write config file.");
    }

    function offsetExists($key)
    {
        return isset($this->configs[$key]);
    }

    function offsetUnset($key)
    {
        unset($this->configs[$key]);
    }
}

(2)configs/database.php

<?php
$config = array(
    'master' => array(
        'type' => 'MySQL',
        'host' => '127.0.0.1',
        'user' => 'root',
        'password' => '123456',
        'dbname' => 'test',
    ),
    'slave' => array(
        'slave1' => array(
            'type' => 'MySQL',
            'host' => '127.0.0.1',
            'user' => 'root',
            'password' => '123456',
            'dbname' => 'test',
        ),
        'slave2' => array(
            'type' => 'MySQL',
            'host' => '127.0.0.1',
            'user' => 'root',
            'password' => '123456',
            'dbname' => 'test',
        ),
    ),
);
return $config;

(3)讀取設定

index.php

<?php
/**
 * 框架入口檔案
 */
define('BASEDIR',__DIR__);//專案根目錄
include BASEDIR.'/Extend/Loader.php';//引入專案自動載入類檔案
spl_autoload_register('ExtendLoader::autoload');//執行自動載入函數,完成類的自動載入

$config = new ExtendConfig(__DIR__.'/configs');
var_dump($config['database']);

(4)瀏覽器顯示:

到此,就可以在程式中隨心所欲的載入組態檔了。

2、在工廠方法中讀取設定,生成可設定化的物件

Extend/Factory.php

<?php
namespace Extend;

class Factory
{
    static function getDatabase($id)
    {
        $key = 'database_'.$id;
        if ($id == 'slave')
        {
            $slaves = Application::getInstance()->config['database']['slave'];
            $db_conf = $slaves[array_rand($slaves)];
        } else {
            $db_conf = Application::getInstance()->config['database'][$id];
        }
        //註冊樹模式儲存及獲取物件
        $db = Register::get($key);
        if (!$db) {
            $db = new DatabaseMySQLi();
            $db->connect($db_conf['host'], $db_conf['user'], $db_conf['password'], $db_conf['dbname']);
            Register::set($key, $db);
        }
        return $db;
    }

}

Extend/Application.php

<?php
namespace Extend;

class Application
{
    public $base_dir;
    protected static $instance;

    public $config;

    protected function __construct($base_dir)
    {
        $this->base_dir = $base_dir;
        $this->config = new Config($base_dir.'/configs');
    }

    static function getInstance($base_dir = '')
    {
        if (empty(self::$instance))
        {
            self::$instance = new self($base_dir);
        }
        return self::$instance;
    }

}

Extend/Config.php

<?php
namespace Extend;

/**
 * 設定類,繼承於php自帶的ArrayAccess介面
 * 允許一個物件以陣列的方式存取
 * Class Config
 * @package Extend
 */
class Config implements ArrayAccess
{
    protected $path;
    protected $configs = array();

    function __construct($path)
    {
        $this->path = $path;
    }

    function offsetGet($key)
    {
        if (empty($this->configs[$key]))
        {
            $file_path = $this->path.'/'.$key.'.php';
            $config = require $file_path;
            $this->configs[$key] = $config;
        }
        return $this->configs[$key];
    }

    function offsetSet($key, $value)
    {
        throw new Exception("cannot write config file.");
    }

    function offsetExists($key)
    {
        return isset($this->configs[$key]);
    }

    function offsetUnset($key)
    {
        unset($this->configs[$key]);
    }
}

以上就是PHP開發自己的框架,你必須知道這些知識點!的詳細內容,更多請關注TW511.COM其它相關文章!