本文為大家介紹常用的三種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
單例模式的優缺點:
<?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?>
單例模式的優缺點:
缺點:1. 難於調試2. 隱藏的依賴關係3. 無法用錯誤類型的資料覆蓋一個單例
二、工廠模式是指工廠模式包含一個專門用來創建其他對象的方法的類,工廠類在多態性編程實踐中是至關重要的,它允許動態的替換類,修改配置,通常會使應用程序更加靈活,熟練掌握工廠模式高階PHP開發人員是很重要的。
工廠模式通常用來返回符合類似介面的不同的類,工廠的一種常見用法就是創建多態的提供者,從而允許我們基於應用程式邏輯或配置設定來決定應實例化哪一個類,例如,可以使用這樣的提供者來擴展一個類,而不需要重構應用程式的其他部分,從而使用新的擴充後的名稱。
通常,工廠模式有一個關鍵的構造,根據一般原則命名為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工厂模式就是用一个工厂方法来替换掉直接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文件代码中都要进行修改。而使用工厂模式,只要在工厂方法或类里面进行修改即可。而且工厂模式是其他设计模式的基础。
对上面的简单工厂模式再进一步优化,比如:
利用工厂类生产对象:
<?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');?>
简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。
要理解工厂模式这个概念,让我们最好谈一下许多开发人员从事大型系统的艰苦历程。在更改一个代码片段时,就会发生问题,系统其他部分 —— 您曾认为完全不相关的部分中也有可能出现级联破坏。
该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和结构。您需要一组模式,使这些类能够相互通信,但不希望将它们紧密绑定在一起,以避免出现联锁。
在大型系统中,许多代码依赖于少数几个关键类。需要更改这些类时,可能会出现困难。例如,假设您有一个从文件读取的 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图:
三、观察者模式
观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:观察者模式是一种事件系统,意味着这一模式允许某个类观察另一个类的状态,当被观察的类状态发生改变的时候,观察类可以收到通知并且做出相应的动作;
现在有两派,有的人建议使用设计模式,有的人不建议使用设计模式!
这就好比写文章一样,有的人喜欢文章按照套路走,比如叙事性质的文章,时间,地点,人物,事件。而有的人喜欢写杂文或者散文,有的人喜欢写诗词!
现在写代码很多地方类似于写文章,但是在有些地方比写文章需要更多的技能!写文章写多了一般也能写出优秀的文章,而代码也一样,写多了也能写出很多有写的代码!
很多时候,我看设计模式的时候,有些设计模式只是吻合我的代码习惯。但是你硬去套它,那么反而适得其反。——很多时候是学会了招式,在应用中不知不觉的使用上这些招式,才能掌握其道,但是也不要拘泥于招式,正所谓“无招胜有招”吗?
我学设计模式的初衷,就是知道有这么个玩意儿?脑子里有这么个印象,也不会生套它!如果设计模式不符合你的习惯对你阅读代码反而是不利的!
观察者模式定义对象的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新!
设计原则
在观察者模式中,会改变的是主题的状态以及观察者的数目。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。——找出程序中会变化的方面,然后将其和固定不变的方面相分离!
主题和观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点! ——针对接口编程,不针对实现编程!
观察者模式利用“组合”将许多观察者组合进主题中。对象(观察者——主题)之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式产生的。 ——多用组合,少用继承!
好了,不说太多废话,直接上代码:
<?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();
总结
当新对象要填入的时候,只需要在主题(又叫可观察者)中进行注册(注册方式很多,你也可以在构造的时候,或者框架访问的接口中进行注册),然后实现代码直接在新对象的接口中进行。这降低了主题对象和观察者对象的耦合度。
好的设计模式不会直接进入你的代码中,而是进入你的大脑中。
更多PHP常用的三种设计模式 相关文章请关注PHP中文网!