本文為大家介紹常用的三種php設計模式:單例模式、原廠模式、觀察者模式,有需要的朋友可以參考下。
一、首先來看,單例模式
所謂單例模式,就是確保某個類只有一個執行個體,而且自行執行個體化並向整個系統提供這個執行個體,即在應用程式中只會有這個類的一個執行個體存在。
通常單例模式用在僅允許資料庫訪問對象的執行個體中,從而防止開啟多個資料庫連接,單例模式是一種常見的設計模式,在電腦系統中,線程池、緩衝、日誌對象、對話方塊、印表機、資料庫操作、顯卡的驅動程式常被設計成單例。
一個單例類應包括以下幾點:
和普通類不同,單例類不能被直接執行個體化,只能是由自身執行個體化。因此,要獲得這樣的限制效果,建構函式必須標記為private。
要讓單例類不被直接執行個體化而能起到作用,就必須為其提供這樣的一個執行個體。因此,就必須要讓單例類擁有一個能儲存類的執行個體的私人靜態成員變數和對應的一個能訪問到執行個體的公用靜態方法。
在PHP中,為防止對單例類對象的複製來打破單例類的上述實現形式,通常還為基提供一個空的私人__clone()方法。好吧,廢話不多說,概括如下
單例模式有以下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 phpecho $ob->getname();//good morning php
單例模式的優缺點:
優點:
1. 改進系統的設計
2. 是對全域變數的一種改進
缺點:
1. 難於調試
2. 隱藏的依賴關係
3. 無法用錯誤類型的資料覆寫一個單例
二、原廠模式
原廠模式就是一種類,是指包含一個專門用來建立其他對象的方法的類,工廠類在多態性編程實踐中是至關重要的,它允許動態替換類,修改配置,通常會使應用程式更加靈活,熟練掌握原廠模式進階PHP開發人員是很重要的。
原廠模式通常用來返回符合類似介面的不同的類,工廠的一種常見用法就是建立多態的提供者,從而允許我們基於應用程式邏輯或者配置設定來決定應執行個體化哪一個類,例如,可以使用這樣的提供者來擴充一個類,而不需要重構應用程式的其他部分,從而使用新的擴充後的名稱 。
通常,原廠模式有一個關鍵的構造,根據一般原則命名為Factory的靜態方法,然而這隻是一種原則,Factory 方法可以任意命名,這個靜態還可以接受任意資料的參數,必須返回一個對象。
具有為您建立對象的某些方法,這樣就可以使用工廠類建立對象,原廠模式在於可以根據輸入參數或者應用程式配置的不同來建立一種專門用來實現化並返回其它類的執行個體的類,而不直接使用new,這樣如果想更改建立的物件類型,只需更改該工廠即可,
先舉個樣本吧:
<?phpclass 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原廠模式就是用一個Factory 方法來替換掉直接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檔案代碼中都要進行修改。而使用原廠模式,只要在Factory 方法或類裡面進行修改即可。而且原廠模式是其他設計模式的基礎。
對上面的簡單原廠模式再進一步最佳化,比如:
利用工廠類生產對象:
<?phpclass 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');?>
簡單原廠模式又稱靜態Factory 方法模式。從命名上就可以看出這個模式一定很簡單。它存在的目的很簡單:定義一個用於建立對象的介面。
要理解原廠模式這個概念,讓我們最好談一下許多開發人員從事大型系統的艱苦曆程。在更改一個程式碼片段時,就會發生問題,系統其他部分 —— 您曾認為完全不相關的部分中也有可能出現級聯破壞。
該問題在於緊密耦合 。系統某個部分中的函數和類嚴重依賴於系統的其他部分中函數和類的行為和結構。您需要一組模式,使這些類能夠相互連信,但不希望將它們緊密綁定在一起,以避免出現聯鎖。
在大型系統中,許多代碼依賴於少數幾個關鍵類。需要更改這些類時,可能會出現困難。例如,假設您有一個從檔案讀取的 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 類代碼發生變動,即:
<?phpclass 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 類也發生變動:
<?phpclass 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圖:
三、觀察者模式
觀察者模式為您提供了避免組件之間緊密耦合的另一種方法。該模式非常簡單:觀察者模式是一種事件系統,意味著這一模式允許某個類觀察另一個類的狀態,當被觀察的類狀態發生改變的時候,觀察類可以收到通知並且做出相應的動作;
現在有兩派,有的人建議使用設計模式,有的人不建議使用設計模式!
這就好比寫文章一樣,有的人喜歡文章按照套路走,比如敘事性質的文章,時間,地點,人物,事件。而有的人喜歡寫雜文或者散文,有的人喜歡寫詩詞!
現在寫代碼很多地方類似於寫文章,但是在有些地方比寫文章需要更多的技能!寫文章寫多了一般也能寫出優秀的文章,而代碼也一樣,寫多了也能寫出很多有寫的代碼!
很多時候,我看設計模式的時候,有些設計模式只是吻合My Code習慣。但是你硬去套它,那麼反而適得其反。——很多時候是學會了招式,在應用中不知不覺的使用上這些招式,才能掌握其道,但是也不要拘泥於招式,正所謂“無招勝有招”嗎?
我學設計模式的初衷,就是知道有這麼個玩意兒?腦子裡有這麼個印象,也不會生套它!如果設計模式不符合你的習慣對你閱讀代碼反而是不利的!
觀察者模式定義對象的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新!
設計原則
在觀察者模式中,會改變的是主題的狀態以及觀察者的數目。用這個模式,你可以改變依賴於主題狀態的對象,卻不必改變主題。——找出程式中會變化的方面,然後將其和固定不變的方面相分離!
主題和觀察者都使用介面:觀察者利用主題的介面向主題註冊,而主題利用觀察者介面通知觀察者。這樣可以讓兩者之間運作正常,又同時具有松耦合的優點! ——針對介面編程,不針對實現編程!
觀察者模式利用“組合”將許多觀察者組合進主題中。對象(觀察者——主題)之間的這種關係不是通過繼承產生的,而是在運行時利用組合的方式產生的。 ——多用組合,少用繼承!
好了,不說太多廢話,直接上代碼:
<?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();
總結
當新對象要填入的時候,只需要在主題(又叫可觀察者)中進行註冊(註冊方式很多,你也可以在構造的時候,或者架構訪問的介面中進行註冊),然後實現代碼直接在新對象的介面中進行。這降低了主題對象和觀察者對象的耦合度。
好的設計模式不會直接進入你的代碼中,而是進入你的大腦中。