php 設計模式之工廠模式、單例模式、註冊樹模式

php 設計模式之工廠模式、單例模式、註冊樹模式

在軟件工程中,建立型設計模式承擔着對象建立的職責,嘗試建立適合程序上下文的對象,對象建立設計模式的產生是因爲軟件工程設計的問題,具體說是向設計中增長複雜度,建立型設計模式解決了程序設計中對象建立的問題。PHP的設計模式有不少種,本文取最簡單的三種模式: 工廠模式、單例模式和註冊樹模式進行簡單的講解。php

工廠模式

用工廠方法或者類生成對象,而不是代碼中直接使用 new 關鍵字mysql

好比我們要作一個鏈接數據庫的操做,平時都是用mysql。那忽然有一天mysql收費了,咋辦?那咱們就用Sqlite做來數據庫。若是以傳統的方式建立一個數據庫鏈接類的話是須要使用關鍵字new MysqlDrive()把這個數據庫實例化,一旦數據庫變動成的話就須要把全部的new MysqlDrive()變動成new SqliteDrive() 這樣替換比較麻煩,而且還容易出錯。因此,下面的工廠模式能夠很好的解決這一問題。git

廢話很少說,演示一遍就明白了github

先看一下目錄結構吧本文演示的所有是遵循PSR-4的編碼規範,固然,是在vendor/目錄下,若是沒有請本身行執行命令composer dumpatuoload 若是您沒有安裝composer那就請看下面文章:sql

《集成 Composer 包依賴管理》數據庫

如下是個人目錄結構: bootstrap

自動忽略DependencyInjection 跟HttpFoundation目錄,這是我計劃下次寫的東西,嘻嘻...設計模式


先看看主要文件的代碼吧:composer

tests/這個目錄是爲了讓我們方便測試的目錄,也就是單元測試目錄框架

文件: tests/bootstrap.php

include_once __DIR__."/../../../composer/ClassLoader.php";

$loader = new \Composer\Autoload\ClassLoader();

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

$classMaps = [
    'Dudulu\\' => [$vendorDir."/src/"]
];

foreach ($classMaps as $namespace => $path) {
    $loader->setPsr4($namespace, $path);
}

$loader->register(true);

我們須要測試的驅動類MysqlDerive.php

文件: src/DesignPatterns\FactoryMode\DB\MysqlDrive.php

namespace Dudulu\DesignPatterns\FactoryMode;

use Dudulu\DesignPatterns\DB\MysqlDrive;

/**
 * Class Factory
 * @package Dudulu\DesignPatterns\FactoryMode
 */
class Factory
{

    /**
     * @return MysqlDrive
     */
    public static function database()
    {
        return new MysqlDrive();
    }
}

爲了強制讓每種驅動都要有一個鏈接方式,因此咱們須要一個Interface。

文件: src/DesignPatterns/DB/Interfaces/DriveInterface.php

namespace Dudulu\DesignPatterns\DB\Interfaces;


/**
 * Interface DriveInterface
 * @package Dudulu\DesignPatterns\DB\Interfaces
 */
interface DriveInterface
{
    /**
     * @return mixed
     */
    public function connection();
}

普通模式

先看一個普通demo,直接實例化MysqlDrive對象

include_once "../bootstrap.php";

use Dudulu\DesignPatterns\DB\MysqlDrive;

class FactoryTest extends PHPUnit_Framework_TestCase {

    public function testConnection()
    {
        $db = new MysqlDrive();

        $this->assertEquals(true, $db->connection());
    }
}

進入src/tests/DesignPattens目錄.

命令行執行: phpunit FactoryTest.php能夠發現這樣是好使的...

工廠模式

工廠模式 是一種類,它具備爲您建立對象的某些方法。您可使用工廠類建立對象,而不直接使用 new。這樣,若是您想要更改所建立的對象類型,只需更改該工廠便可。使用該工廠的全部代碼會自動更改。

首先我們先建立一個Factory.php做爲工廠類,而後裏邊實現一個靜態方法對相數據庫驅動進行實例化。

文件: src/DesignPatterns/FactoryMode/Factory.php

namespace Dudulu\DesignPatterns\FactoryMode;

use Dudulu\DesignPatterns\DB\MysqlDrive;

/**
 * Class Factory
 * @package Dudulu\DesignPatterns\FactoryMode
 */
class Factory
{

    /**
     * @return MysqlDrive
     */
    public static function database()
    {
        return new MysqlDrive();
    }
}

建立完後,我們回到單元測試文件FactoryTest.php

文件: tests/DesignPattens/FactoryTest.php

include_once "../bootstrap.php";

use Dudulu\DesignPatterns\FactoryMode\Factory;

class FactoryTest extends PHPUnit_Framework_TestCase {

    public function testConnection()
    {
        $db = Factory::database();

        $this->assertEquals(true, $db->connection());
    }
}

再執行一下單元測試命令發現,也能返回成功,這樣的話咱們就能很方便的修改任何驅動了。若是咱們從mysql換到sqlite了,那麼,我們主要的代碼都不須要更變,只要在Factory.phpconnection方法的邊接方式修改完就行了,很是方便。

單例模式

使某個類的對象僅容許建立一個

某些應用程序資源是獨佔的,由於有且只有一個此類型的資源。好比,數據庫的鏈接的獨佔。但願在應用程序中共享數據庫鏈接,由於在保持鏈接打開或關閉時,它是一種開銷,在獲取單個頁面的過程當中更是如此。

好比我們鏈接數據庫時,若是不使用單例模式,多個地方都對數據類進行了實例化,那麼這樣會造能不少資源浪費,爲了解決這問題,對於數據庫類咱們只須要實例化一次,後面再次調用它是若是已經實例化,那就直接返回。

來,咱們拿一個類來玩一玩...

文件: src/DesignPatterns/SingletonMode/SingletonDB.php

咱們建立這個類,而後將構造方法私有化,這樣的話咱們就沒法使用new關鍵字對這個類進行實例化了。

namespace Dudulu\DesignPatterns\SingletonMode;


use Dudulu\DesignPatterns\DB\Interfaces\DriveInterface;

/**
 * Class SingletonDB
 * @package Dudulu\DesignPatterns\SingletonMode
 */
class SingletonDB implements DriveInterface
{

    /**
     * SingletonDB constructor.
     */
    private function __construct()
    {

    }

    /**
     * @return bool
     */
    public function connection()
    {
        return true;
    }
}

那麼咱們要如何使用這個對象呢?咱們須要一個受保護的成員和一個靜態方法:

/**
     * @var SingletonDB
     */
    protected static $db;
    
    /**
     * @return SingletonDB
     */
    public static function getInstance()
    {
        if (self::$db) {
            return self::$db;
        }
        self::$db = new self();
        return self::$db;
    }

這個方法很好理解,簡單意思就是若是當前屬性已經被設置過了,那就再也不進行實例化,而是直接返回,不然實例化當前對象並返回。

與測試上面的方法同樣,測玩玩...

建立測試的文件: tests/DesignPattens/SingletonDBTest.php

include_once "../bootstrap.php";

use Dudulu\DesignPatterns\SingletonMode\SingletonDB;

/**
 * Class SingletonDBTest
 */
class SingletonDBTest extends PHPUnit_Framework_TestCase
{
    /**
     * @return void
     */
    public function testConnection()
    {
        $db = SingletonDB::getInstance();

        $this->assertEquals(true, $db->connection());
    }
}

執行命令: phpunit SingletonDBTest.php 發現也是能夠執行成功的。

若是不信的話,你能夠試試多執行幾回SingletonDB::getInstance(); 而後在 getInstance() 方法體裏作一個計數器,看看它實例化過幾回。

註冊樹模式

主要用來解決全局共享和交換對象

這個也很好理解,由於咱們在框架中常常用到。

註冊樹模式固然也叫註冊模式,註冊器模式。之因此我在這裏矯情一下它的名稱,是由於我感受註冊樹這個名稱更容易讓人理解。註冊樹模式經過將對象實例註冊到一棵全局的對象樹上,須要的時候從對象樹上採摘的模式設計方法。

我們結合工廠模式及單例模式作一個小例子:

建立文件: src/DesignPatterns/RegisterMode/Register.php

namespace Dudulu\DesignPatterns\RegisterMode;


/**
 * Class Register
 * @package Dudulu\DesignPatterns\RegisterMode
 */
class Register
{
    /**
     * @var array
     */
    protected static $classMaps = [];

    /**
     * @param $alias
     * @param $class
     * @return void
     */
    public static function set($alias, $class )
    {
        self::$classMaps[$alias] = $class;
    }

    /**
     * @param $alias
     * @return mixed
     */
    public static function get($alias )
    {
        return self::$classMaps[$alias];
    }
}

而後建立一個單例模式的DB類DemoDB.php:

文件: src/DesignPatterns/DB/DemoDB.php

namespace Dudulu\DesignPatterns\DB;


use Dudulu\DesignPatterns\DB\Interfaces\DriveInterface;

class DemoDB implements DriveInterface
{
    /**
     * @var DemoDB
     */
    protected static $db;

    /**
     * SingletonDB constructor.
     */
    private function __construct()
    {

    }

    /**
     * @return DemoDB
     */
    public static function getInstance()
    {
        if (self::$db) {
            return self::$db;
        }
        self::$db = new self();
        return self::$db;
    }

    /**
     * @return bool
     */
    public function connection()
    {
        return true;
    }
}

再工廠模式文件上加入註冊方式代碼:

文件: src/DesignPatterns/FactoryMode/Factory.php

use Dudulu\DesignPatterns\RegisterMode\Register;

    /**
     * @return DemoDB
     */
    public static function testDb()
    {
        $db = DemoDB::getInstance();

        Register::set('DB', $db);

        return $db;
    }

最後我們驗證一下:

tests/目錄下建立RegisterTest.php文件

include_once "../bootstrap.php";

use Dudulu\DesignPatterns\RegisterMode\Register;
use Dudulu\DesignPatterns\DB\DemoDB;

/**
 * Class RegisterTest
 */
class RegisterTest extends PHPUnit_Framework_TestCase
{
    /**
     * @return void
     */
    public function testConnection()
    {
        Register::set('DB', DemoDB::getInstance());

        $db = Register::get('DB');

        $this->assertEquals(true, $db->connection());
    }
}

OK 走一個phpunit RegisterTest.php

返回OK,好棒好棒...☆〜(ゝ。∂)

  __
  / )))    _
`/ イ~   (((ヽ
(  ノ      ̄Y\
| (\ ∧_∧ | )
ヽ ヽ`( `o´ )/ノ/
 \ | ⌒Y⌒ / /
  |ヽ  |  ノ/
  \トー仝ーイ
   |

GitHub: https://github.com/icowan/dudulu

若是有時間,下次我再寫些關於其餘設計模式的文章...

原文地址: 設計模式之工廠模式、單例模式、註冊樹模式
歡迎關注我站點: LatteCake
歡迎關注公衆號: 聰聰實驗室

相關文章
相關標籤/搜索