本文為大家介紹常用的三種php設(shè)計(jì)模式:?jiǎn)卫J?、工廠模式、觀察者模式,有需要的朋友可以參考下。 一、首先來看,單例模式 所謂單例模式,就是確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例,即在應(yīng)用程序中只會(huì)有這個(gè)類的一個(gè)實(shí)例存在。 一個(gè)單例類應(yīng)包括以下幾點(diǎn): 單例模式有以下3個(gè)特點(diǎn): 1.只能有一個(gè)實(shí)例,必須擁有一個(gè)構(gòu)造函數(shù),并且必須被標(biāo)記為private 2.必須自行創(chuàng)建這個(gè)實(shí)例,擁有一個(gè)保存類的實(shí)例的靜態(tài)成員變量 3.必須給其他對(duì)象提供這一實(shí)例,擁有一個(gè)訪問這個(gè)實(shí)例的公共的靜態(tài)方法 單例類不能再其它類中直接實(shí)例化,只能被其自身實(shí)例化。它不會(huì)創(chuàng)建實(shí)例副本,而是會(huì)向單例類內(nèi)部存儲(chǔ)的實(shí)例返回一個(gè)引用 那么為什么要使用PHP單例模式? PHP一個(gè)主要應(yīng)用場(chǎng)合就是應(yīng)用程序與數(shù)據(jù)庫(kù)打交道的場(chǎng)景,在一個(gè)應(yīng)用中會(huì)存在大量的數(shù)據(jù)庫(kù)操作,針對(duì)數(shù)據(jù)庫(kù)句柄連接數(shù)據(jù)庫(kù)的行為,使用單例模式可以避免大量的new操作。因?yàn)槊恳淮蝞ew操作都會(huì)消耗系統(tǒng)和內(nèi)存的資源。 在以往的項(xiàng)目開發(fā)中,沒使用單例模式前的情況如下: //初始化一個(gè)數(shù)據(jù)庫(kù)句柄
$db = new DB(...);
//比如有個(gè)應(yīng)用場(chǎng)景是添加一條評(píng)論信息
$db->addComment();
......
//如果我們要在另一地方使用這個(gè)評(píng)論信息,這時(shí)要用到數(shù)據(jù)庫(kù)句柄資源,可能會(huì)這么做
......
function comment() {
$db = new DB(...);
$db->getCommentInfo();
......
//可能有些朋友也許會(huì)說,可以直接使用global關(guān)鍵字!
global $db;
......
的確global可以解決問題,也起到單例模式的作用,但在OOP中,我們建議拒絕這種編碼。因?yàn)間lobal存在安全隱患(全局變量不受保護(hù)的本質(zhì))。 全局變量是面向?qū)ο蟪绦騿T遇到的引發(fā)BUG的主要原因之一。這是因?yàn)槿肿兞繉㈩惱売谔囟ǖ沫h(huán)境,破壞了封裝。如果新的應(yīng)用程序無法保證一開始就定義了相同的全局變量,那么一個(gè)依賴于全局變量的類就無法從一個(gè)應(yīng)用程序中提取出來并應(yīng)用到新應(yīng)用程序中。 確切的講,單例模式恰恰是對(duì)全局變量的一種改進(jìn),避免那些存儲(chǔ)唯一實(shí)例的全局變量污染命名空間。你無法用錯(cuò)誤類型的數(shù)據(jù)覆寫一個(gè)單例。這種保護(hù)在不支持命名空間的PHP版本里尤其重要。因?yàn)樵赑HP中命名沖突會(huì)在編譯時(shí)被捕獲,并使腳本停止運(yùn)行。 我們用單例模式改進(jìn)下示例: class Single { private $name;//聲明一個(gè)私有的實(shí)例變量 private function __construct(){//聲明私有構(gòu)造方法為了防止外部代碼使用new來創(chuàng)建對(duì)象。 } static public $instance;//聲明一個(gè)靜態(tài)變量(保存在類中唯一的一個(gè)實(shí)例) static public function getinstance(){//聲明一個(gè)getinstance()靜態(tài)方法,用于檢測(cè)是否有實(shí)例對(duì)象 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 單例模式的優(yōu)缺點(diǎn): 優(yōu)點(diǎn): 1. 改進(jìn)系統(tǒng)的設(shè)計(jì) 2. 是對(duì)全局變量的一種改進(jìn) 缺點(diǎn): 1. 難于調(diào)試 2. 隱藏的依賴關(guān)系 3. 無法用錯(cuò)誤類型的數(shù)據(jù)覆寫一個(gè)單例 二、工廠模式 工廠模式就是一種類,是指包含一個(gè)專門用來創(chuàng)建其他對(duì)象的方法的類,工廠類在多態(tài)性編程實(shí)踐中是至關(guān)重要的,它允許動(dòng)態(tài)的替換類,修改配置,通常會(huì)使應(yīng)用程序更加靈活,熟練掌握工廠模式高級(jí)PHP開發(fā)人員是很重要的。 工廠模式通常用來返回符合類似接口的不同的類,工廠的一種常見用法就是創(chuàng)建多態(tài)的提供者,從而允許我們基于應(yīng)用程序邏輯或者配置設(shè)置來決定應(yīng)實(shí)例化哪一個(gè)類,例如,可以使用這樣的提供者來擴(kuò)展一個(gè)類,而不需要重構(gòu)應(yīng)用程序的其他部分,從而使用新的擴(kuò)展后的名稱 。 通常,工廠模式有一個(gè)關(guān)鍵的構(gòu)造,根據(jù)一般原則命名為Factory的靜態(tài)方法,然而這只是一種原則,工廠方法可以任意命名,這個(gè)靜態(tài)還可以接受任意數(shù)據(jù)的參數(shù),必須返回一個(gè)對(duì)象。 具有為您創(chuàng)建對(duì)象的某些方法,這樣就可以使用工廠類創(chuàng)建對(duì)象,工廠模式在于可以根據(jù)輸入?yún)?shù)或者應(yīng)用程序配置的不同來創(chuàng)建一種專門用來實(shí)現(xiàn)化并返回其它類的實(shí)例的類,而不直接使用new,這樣如果想更改創(chuàng)建的對(duì)象類型,只需更改該工廠即可, 先舉個(gè)示例吧: <?php
class Factory {//創(chuàng)建一個(gè)基本的工廠類
static public function fac($id){//創(chuàng)建一個(gè)返回對(duì)象實(shí)例的靜態(tài)方法
if(1 == $id) return new A();
elseif(2==$id) return new B();
elseif(3==$id) return new C();
return new D();
}
}
interface FetchName {//創(chuàng)建一個(gè)接口
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);//調(diào)用工廠類中的方法
if($o instanceof FetchName){
echo $o->getname();//DDDDD
}
$p=Factory::fac(3);
echo $p->getname();//CCCCC
?>
個(gè)人意見,再說簡(jiǎn)單點(diǎn)吧,PHP工廠模式就是用一個(gè)工廠方法來替換掉直接new對(duì)象的操作,就是為方便擴(kuò)展,方便使用,在新增實(shí)現(xiàn)基類中的類中方法時(shí)候,那么在工廠類中無需修改,傳入?yún)?shù)可以直接使用,具體就是跳過工廠類修改,直接使用工廠類輸出想要的結(jié)果。在傳統(tǒng)習(xí)慣中,如果要生成一個(gè)類的話,在代碼中直接new一個(gè)對(duì)象,比如: class Database{ } $db = new Database(); 下面介紹工廠模式的操作方法: class Database{
}
//創(chuàng)建一個(gè)工廠類
class Factory
{
//創(chuàng)建一個(gè)靜態(tài)方法
static function createDatabase(){
$db = new Database;
return $db;
}
}
那么,當(dāng)我們想創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)類的話,就可以使用這樣的方法: <?php $db = Factory::createDatabase(); ?> 簡(jiǎn)單工廠模式比直接new一個(gè)對(duì)象的好處是,比如Database這個(gè)類在很多php文件中都有使用到,當(dāng)Database這個(gè)類發(fā)生了某些變更,比如修改了類名、或者一些參數(shù)發(fā)生了變化,那這時(shí)候如果你使用的是$db = new Database這種傳統(tǒng)方法生成對(duì)象,那么在所有包含這種生成對(duì)象的php文件代碼中都要進(jìn)行修改。而使用工廠模式,只要在工廠方法或類里面進(jìn)行修改即可。而且工廠模式是其他設(shè)計(jì)模式的基礎(chǔ)。 利用工廠類生產(chǎn)對(duì)象: <?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');
?>
簡(jiǎn)單工廠模式又稱靜態(tài)工廠方法模式。從命名上就可以看出這個(gè)模式一定很簡(jiǎn)單。它存在的目的很簡(jiǎn)單:定義一個(gè)用于創(chuàng)建對(duì)象的接口。 看下實(shí)例: <?php interface IUser { function getName(); } class User implements IUser { public $id; public function __construct( $id ) { } public function getName() { return 'Fantasy'; } } ?> 傳統(tǒng)方法使用 User 類,一般都是這樣: <?php
//在頁(yè)面1
$obj = new User(1);
//在頁(yè)面2
$obj2 = new User(2);
//在頁(yè)面3
$obj3 = new User(3);
....
?>
這時(shí)候,由于新的需求,使得User類要新增個(gè)參數(shù)或者User類名稱發(fā)生變化,User 類代碼發(fā)生變動(dòng),即: <?php class User implements IUser { public $id,$pre; public function __construct( $id , $pre = '') {...} public function getName() { return $this->pre.'Fantasy'; } } ?> 接著,恐怖的事情發(fā)生了,假設(shè)之前有 100 個(gè)頁(yè)面引用了之前的 User 類,那么這 100 個(gè)頁(yè)面都要發(fā)生相應(yīng)的改動(dòng): //在頁(yè)面1
$obj = new User(1,'aaa');
//在頁(yè)面2
$obj = new User(2,'aaa');
//在頁(yè)面3
$obj = new User(3,'aaa');
...
本來是一個(gè)小小的改動(dòng),但因緊密耦合的原因使得改動(dòng)大吐血。而使用工廠模式則可以避免發(fā)生這種情況: //User類為變動(dòng)前 class UserFactory { public static function Create( $id ) { return new User( $id ); } } //頁(yè)面1 $uo1 = UserFactory::Create( 1 ); //頁(yè)面2 $uo12 = UserFactory::Create( 2 ); .... 這時(shí)候需求變動(dòng),User 類也發(fā)生變動(dòng): <?php
class User implements IUser
{
public $id,$pre;
public function __construct( $id , $pre = '') {...}
public function getName()
{
return $this->pre.'Jack';
}
}
?>
但是,我們不再需要去改動(dòng)這 100 個(gè)頁(yè)面,我們要改的僅僅是這個(gè)工廠類: // class UserFactory { public static function Create( $id,$pre = 'aaa' ) { return new User( $id ,$pre); } } 其他100個(gè)頁(yè)面不用做任何改動(dòng),這就是工廠設(shè)計(jì)模式帶來的好處??聪耈ML圖: 三、觀察者模式 現(xiàn)在有兩派,有的人建議使用設(shè)計(jì)模式,有的人不建議使用設(shè)計(jì)模式!
這就好比寫文章一樣,有的人喜歡文章按照套路走,比如敘事性質(zhì)的文章,時(shí)間,地點(diǎn),人物,事件。而有的人喜歡寫雜文或者散文,有的人喜歡寫詩(shī)詞! 現(xiàn)在寫代碼很多地方類似于寫文章,但是在有些地方比寫文章需要更多的技能!寫文章寫多了一般也能寫出優(yōu)秀的文章,而代碼也一樣,寫多了也能寫出很多有寫的代碼! 很多時(shí)候,我看設(shè)計(jì)模式的時(shí)候,有些設(shè)計(jì)模式只是吻合我的代碼習(xí)慣。但是你硬去套它,那么反而適得其反?!芏鄷r(shí)候是學(xué)會(huì)了招式,在應(yīng)用中不知不覺的使用上這些招式,才能掌握其道,但是也不要拘泥于招式,正所謂“無招勝有招”嗎? 我學(xué)設(shè)計(jì)模式的初衷,就是知道有這么個(gè)玩意兒?腦子里有這么個(gè)印象,也不會(huì)生套它!如果設(shè)計(jì)模式不符合你的習(xí)慣對(duì)你閱讀代碼反而是不利的!
觀察者模式定義對(duì)象的一對(duì)多依賴,這樣一來,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴者都會(huì)收到通知并自動(dòng)更新!
設(shè)計(jì)原則
在觀察者模式中,會(huì)改變的是主題的狀態(tài)以及觀察者的數(shù)目。用這個(gè)模式,你可以改變依賴于主題狀態(tài)的對(duì)象,卻不必改變主題?!页龀绦蛑袝?huì)變化的方面,然后將其和固定不變的方面相分離!
主題和觀察者都使用接口:觀察者利用主題的接口向主題注冊(cè),而主題利用觀察者接口通知觀察者。這樣可以讓兩者之間運(yùn)作正常,又同時(shí)具有松耦合的優(yōu)點(diǎn)! ——針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程!
觀察者模式利用“組合”將許多觀察者組合進(jìn)主題中。對(duì)象(觀察者——主題)之間的這種關(guān)系不是通過繼承產(chǎn)生的,而是在運(yùn)行時(shí)利用組合的方式產(chǎn)生的。 ——多用組合,少用繼承!
好了,不說太多廢話,直接上代碼:
<?php
/**
* 觀察者模式
* @author: Fantasy
* @date: 2017/02/17
*/
class Paper{ /* 主題 */
private $_observers = array();
public function register($sub){ /* 注冊(cè)觀察者 */
$this->_observers[] = $sub;
}
public function trigger(){ /* 外部統(tǒng)一訪問 */
if(!empty($this->_observers)){
foreach($this->_observers as $observer){
$observer->update();
}
}
}
}
/**
* 觀察者要實(shí)現(xiàn)的接口
*/
interface Observerable{
public function update();
}
class Subscriber implements Observerable{
public function update(){
echo 'Callback\n';
}
}
?>
下面是測(cè)試代碼: /* 測(cè)試 */ $paper = new Paper(); $paper->register(new Subscriber()); //$paper->register(new Subscriber1()); //$paper->register(new Subscriber2()); $paper->trigger(); 總結(jié)
當(dāng)新對(duì)象要填入的時(shí)候,只需要在主題(又叫可觀察者)中進(jìn)行注冊(cè)(注冊(cè)方式很多,你也可以在構(gòu)造的時(shí)候,或者框架訪問的接口中進(jìn)行注冊(cè)),然后實(shí)現(xiàn)代碼直接在新對(duì)象的接口中進(jìn)行。這降低了主題對(duì)象和觀察者對(duì)象的耦合度。
好的設(shè)計(jì)模式不會(huì)直接進(jìn)入你的代碼中,而是進(jìn)入你的大腦中。
|
|