PHP經常使用的三種設計模式

本文爲你們介紹經常使用的三種php設計模式:單例模式、工廠模式、觀察者模式,有須要的朋友能夠參考下。php

1、首先來看,單例模式mysql

所謂單例模式,就是確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例,即在應用程序中只會有這個類的一個實例存在。
一般單例模式用在僅容許數據庫訪問對象的實例中,從而防止打開多個數據庫鏈接,單例模式是一種常見的設計模式,在計算機系統中,線程池、緩存、日誌對象、對話框、打印機、數據庫操做、顯卡的驅動程序常被設計成單例。程序員

一個單例類應包括如下幾點:
和普通類不一樣,單例類不能被直接實例化,只能是由自身實例化。所以,要得到這樣的限制效果,構造函數必須標記爲private。
要讓單例類不被直接實例化而能起到做用,就必須爲其提供這樣的一個實例。所以,就必需要讓單例類擁有一個能保存類的實例的私有靜態成員變量和對應的一個能訪問到實例的公共靜態方法。
在PHP中,爲防止對單例類對象的克隆來打破單例類的上述實現形式,一般還爲基提供一個空的私有__clone()方法。好吧,廢話很少說,歸納以下sql

單例模式有如下3個特色:數據庫

1.只能有一個實例,必須擁有一個構造函數,而且必須被標記爲private編程

2.必須自行建立這個實例,擁有一個保存類的實例的靜態成員變量設計模式

3.必須給其餘對象提供這一實例,擁有一個訪問這個實例的公共的靜態方法緩存

單例類不能再其它類中直接實例化,只能被其自身實例化。它不會建立實例副本,而是會向單例類內部存儲的實例返回一個引用安全

那麼爲何要使用PHP單例模式?框架

PHP一個主要應用場合就是應用程序與數據庫打交道的場景,在一個應用中會存在大量的數據庫操做,針對數據庫句柄鏈接數據庫的行爲,使用單例模式能夠避免大量的new操做。由於每一次new操做都會消耗系統和內存的資源。

在以往的項目開發中,沒使用單例模式前的狀況以下:

複製代碼
//初始化一個數據庫句柄
$db = new DB(...);
//好比有個應用場景是添加一條評論信息
$db->addComment();
......
//若是咱們要在另外一地方使用這個評論信息,這時要用到數據庫句柄資源,可能會這麼作
......
function comment() {
   $db = new DB(...);
   $db->getCommentInfo();
......
//可能有些朋友也許會說,能夠直接使用global關鍵字!
   global $db;
......
複製代碼

的確global能夠解決問題,也起到單例模式的做用,但在OOP中,咱們建議拒絕這種編碼。由於global存在安全隱患(全局變量不受保護的本質)。

全局變量是面向對象程序員遇到的引起BUG的主要緣由之一。這是由於全局變量將類捆綁於特定的環境,破壞了封裝。若是新的應用程序沒法保證一開始就定義了相同的全局變量,那麼一個依賴於全局變量的類就沒法從一個應用程序中提取出來並應用到新應用程序中。

確切的講,單例模式偏偏是對全局變量的一種改進,避免那些存儲惟一實例的全局變量污染命名空間。你沒法用錯誤類型的數據覆寫一個單例。這種保護在不支持命名空間的PHP版本里尤爲重要。由於在PHP中命名衝突會在編譯時被捕獲,並使腳本中止運行。

咱們用單例模式改進下示例:

複製代碼
class Single {
    private $name;//聲明一個私有的實例變量
    private function __construct(){//聲明私有構造方法爲了防止外部代碼使用new來建立對象。
    
    }

    static public $instance;//聲明一個靜態變量(保存在類中惟一的一個實例)
    static public function getinstance(){//聲明一個getinstance()靜態方法,用於檢測是否有實例對象
        if(!self::$instance) self::$instance = new self();
        return self::$instance;
    }

    public function setname($n){ $this->name = $n; }
    public function getname(){ return $this->name; }
}

$oa = Single::getinstance();
$ob = Single::getinstance();
$oa->setname('hello php world');
$ob->setname('good morning php');
echo $oa->getname();//good morning php
echo $ob->getname();//good morning php
複製代碼

單例模式的優缺點:

優勢:

1. 改進系統的設計

2. 是對全局變量的一種改進

缺點:

1. 難於調試

2. 隱藏的依賴關係

3. 沒法用錯誤類型的數據覆寫一個單例

2、工廠模式

 工廠模式就是一種類,是指包含一個專門用來建立其餘對象的方法的類,工廠類在多態性編程實踐中是相當重要的,它容許動態的替換類,修改配置,一般會使應用程序更加靈活,熟練掌握工廠模式高級PHP開發人員是很重要的。

 工廠模式一般用來返回符合相似接口的不一樣的類,工廠的一種常見用法就是建立多態的提供者,從而容許咱們基於應用程序邏輯或者配置設置來決定應實例化哪個類,例如,可使用這樣的提供者來擴展一個類,而不須要重構應用程序的其餘部分,從而使用新的擴展後的名稱 。

一般,工廠模式有一個關鍵的構造,根據通常原則命名爲Factory的靜態方法,然而這只是一種原則,工廠方法能夠任意命名,這個靜態還能夠接受任意數據的參數,必須返回一個對象。

具備爲您建立對象的某些方法,這樣就可使用工廠類建立對象,工廠模式在於能夠根據輸入參數或者應用程序配置的不一樣來建立一種專門用來實現化並返回其它類的實例的類,而不直接使用new,這樣若是想更改建立的對象類型,只需更改該工廠便可,

先舉個示例吧:

複製代碼
<?php
class Factory {//建立一個基本的工廠類
    static public function fac($id){//建立一個返回對象實例的靜態方法
        if(1 == $id) return new A();
        elseif(2==$id) return new B();
        elseif(3==$id) return new C();
        return new D();
    }
}

interface FetchName {//建立一個接口
    public function getname();//
}

class A implements FetchName{
    private $name = "AAAAA";
    public function getname(){ return $this->name; }
}

class C implements FetchName{
    private $name = "CCCCC";
    public function getname(){ return $this->name; }
}
class B implements FetchName{
    private $name = "BBBBB";
    public function getname(){ return $this->name; }
}

class D implements FetchName{
    private $name = "DDDDD";
    public function getname(){ return $this->name; }
}


$o = Factory::fac(6);//調用工廠類中的方法
if($o instanceof FetchName){
  echo  $o->getname();//DDDDD
}

$p=Factory::fac(3);
echo $p->getname();//CCCCC

?>
複製代碼

我的意見,再說簡單點吧,PHP工廠模式就是用一個工廠方法來替換掉直接new對象的操做,就是爲方便擴展,方便使用,在新增實現基類中的類中方法時候,那麼在工廠類中無需修改,傳入參數能夠直接使用,具體就是跳過工廠類修改,直接使用工廠類輸出想要的結果。在傳統習慣中,若是要生成一個類的話,在代碼中直接new一個對象,好比:

class Database{
    
}
 
$db = new Database();

下面介紹工廠模式的操做方法:

複製代碼
class Database{  
 
}
 
//建立一個工廠類
class Factory
{
   //建立一個靜態方法
   static function createDatabase(){
       $db = new Database;
       return $db;
   }
}
複製代碼

那麼,當咱們想建立一個數據庫類的話,就可使用這樣的方法:

<?php 
    $db = Factory::createDatabase();
?>

簡單工廠模式比直接new一個對象的好處是,好比Database這個類在不少php文件中都有使用到,當Database這個類發生了某些變動,好比修改了類名、或者一些參數發生了變化,那這時候若是你使用的是$db = new Database這種傳統方法生成對象,那麼在全部包含這種生成對象的php文件代碼中都要進行修改。而使用工廠模式,只要在工廠方法或類裏面進行修改便可。並且工廠模式是其餘設計模式的基礎。
對上面的簡單工廠模式再進一步優化,好比:

 利用工廠類生產對象:

複製代碼
<?php
class Example
{
    // The parameterized factory method
    public static function factory($type)
    {
        if (include_once 'Drivers/' . $type . '.php') {
            $classname = 'Driver_' . $type;
            return new $classname;
        } else {
            throw new Exception('Driver not found');
        }
    }
}
 
// Load a MySQL Driver
$mysql = Example::factory('MySQL');
 
// Load an SQLite Driver
$sqlite = Example::factory('SQLite');
?>
複製代碼

簡單工廠模式又稱靜態工廠方法模式。從命名上就能夠看出這個模式必定很簡單。它存在的目的很簡單:定義一個用於建立對象的接口。
要理解工廠模式這個概念,讓咱們最好談一下許多開發人員從事大型系統的艱苦歷程。在更改一個代碼片斷時,就會發生問題,系統其餘部分 —— 您曾認爲徹底不相關的部分中也有可能出現級聯破壞。
該問題在於緊密耦合 。系統某個部分中的函數和類嚴重依賴於系統的其餘部分中函數和類的行爲和結構。您須要一組模式,使這些類可以相互通訊,但不但願將它們緊密綁定在一塊兒,以免出現聯鎖。
在大型系統中,許多代碼依賴於少數幾個關鍵類。須要更改這些類時,可能會出現困難。例如,假設您有一個從文件讀取的 User 類。您但願將其更改成從數據庫讀取的其餘類,可是,全部的代碼都引用從文件讀取的原始類。這時候,使用工廠模式會很方便。

看下實例:

複製代碼
<?php
 
    interface IUser
    {
      function getName();
    }
 
    class User implements IUser
    {        
      public $id;
      public function __construct( $id ) { }
 
      public function getName()
      {
        return "Fantasy";
      }
    }
?>
複製代碼

傳統方法使用 User 類,通常都是這樣:

複製代碼
<?php
//在頁面1
$obj = new User(1);
 
//在頁面2
$obj2 = new User(2);
 
//在頁面3
$obj3 = new User(3);
....

?>
複製代碼

這時候,因爲新的需求,使得User類要新增個參數或者User類名稱發生變化,User 類代碼發生變更,即:

複製代碼
<?php
class User implements IUser
{
  public $id,$pre;
  public function __construct( $id , $pre = '') {...}
 
  public function getName()
  {
    return $this->pre."Fantasy";
  }
}

?>
複製代碼

接着,恐怖的事情發生了,假設以前有 100 個頁面引用了以前的 User 類,那麼這 100 個頁面都要發生相應的改動:

複製代碼
//在頁面1
$obj = new User(1,'aaa');
 
//在頁面2
$obj = new User(2,'aaa');
 
//在頁面3
$obj = new User(3,'aaa');
...
複製代碼

原本是一個小小的改動,但因緊密耦合的緣由使得改動大吐血。而使用工廠模式則能夠避免發生這種狀況:

複製代碼
//User類爲變更前
class UserFactory
{
  public static function Create( $id )
  {
    return new User( $id );
  }
}
 
//頁面1
$uo1 = UserFactory::Create( 1 );
 
//頁面2
$uo12 = UserFactory::Create( 2 );
....
複製代碼

這時候需求變更,User 類也發生變更:

複製代碼
<?php
class User implements IUser
{
  public $id,$pre;
  public function __construct( $id , $pre = '') {...}
 
  public function getName()
  {
    return $this->pre."Jack";
  }
}
?>
複製代碼

可是,咱們再也不須要去改動這 100 個頁面,咱們要改的僅僅是這個工廠類:

複製代碼
//
class UserFactory
{
  public static function Create( $id,$pre = 'aaa' )
  {
    return new User( $id ,$pre);
  }
}
複製代碼

其餘100個頁面不用作任何改動,這就是工廠設計模式帶來的好處。看下UML圖:

工廠模式uml圖

3、觀察者模式
觀察者模式爲您提供了避免組件之間緊密耦合的另外一種方法。該模式很是簡單:觀察者模式是一種事件系統,意味着這一模式容許某個類觀察另外一個類的狀態,當被觀察的類狀態發生改變的時候,觀察類能夠收到通知而且作出相應的動做;

如今有兩派,有的人建議使用設計模式,有的人不建議使用設計模式!
這就比如寫文章同樣,有的人喜歡文章按照套路走,好比敘事性質的文章,時間,地點,人物,事件。而有的人喜歡寫雜文或者散文,有的人喜歡寫詩詞!
如今寫代碼不少地方相似於寫文章,可是在有些地方比寫文章須要更多的技能!寫文章寫多了通常也能寫出優秀的文章,而代碼也同樣,寫多了也能寫出不少有寫的代碼!
不少時候,我看設計模式的時候,有些設計模式只是吻合個人代碼習慣。可是你硬去套它,那麼反而拔苗助長。——不少時候是學會了招式,在應用中不知不覺的使用上這些招式,才能掌握其道,可是也不要拘泥於招式,正所謂「無招勝有招」嗎?
我學設計模式的初衷,就是知道有這麼個玩意兒?腦子裏有這麼個印象,也不會生套它!若是設計模式不符合你的習慣對你閱讀代碼反而是不利的!
觀察者模式定義對象的一對多依賴,這樣一來,當一個對象改變狀態時,它的全部依賴者都會收到通知並自動更新!
設計原則 

在觀察者模式中,會改變的是主題的狀態以及觀察者的數目。用這個模式,你能夠改變依賴於主題狀態的對象,卻沒必要改變主題。——找出程序中會變化的方面,而後將其和固定不變的方面相分離!
 
主題和觀察者都使用接口:觀察者利用主題的接口向主題註冊,而主題利用觀察者接口通知觀察者。這樣可讓二者之間運做正常,又同時具備鬆耦合的優勢! ——針對接口編程,不針對實現編程!
 
觀察者模式利用「組合」將許多觀察者組合進主題中。對象(觀察者——主題)之間的這種關係不是經過繼承產生的,而是在運行時利用組合的方式產生的。 ——多用組合,少用繼承!
好了,不說太多廢話,直接上代碼:
複製代碼
<?php
/**
 * 觀察者模式
 * @author: Fantasy
 * @date: 2017/02/17
 */
 
class Paper{ /* 主題    */
    private $_observers = array();
 
    public function register($sub){ /*  註冊觀察者 */
        $this->_observers[] = $sub;
    }
     
    public function trigger(){  /*  外部統一訪問    */
        if(!empty($this->_observers)){
            foreach($this->_observers as $observer){
                $observer->update();
            }
        }
    }
}
 
/**
 * 觀察者要實現的接口
 */
interface Observerable{
    public function update();
}
 
class Subscriber implements Observerable{
    public function update(){
        echo "Callback\n";
    }
}
?>
複製代碼

下面是測試代碼:

複製代碼
/*  測試    */
$paper = new Paper();
$paper->register(new Subscriber());
//$paper->register(new Subscriber1());
//$paper->register(new Subscriber2());
$paper->trigger();
複製代碼
總結
       當新對象要填入的時候,只須要在主題(又叫可觀察者)中進行註冊(註冊方式不少,你也能夠在構造的時候,或者框架訪問的接口中進行註冊),而後實現代碼直接在新對象的接口中進行。這下降了主題對象和觀察者對象的耦合度。
好的設計模式不會直接進入你的代碼中,而是進入你的大腦中。
相關文章
相關標籤/搜索