自制PHP框架之設(shè)計模式
為什么要使用設(shè)計模式?
設(shè)計模式,我的理解是為了達(dá)到“可復(fù)用”這個目標(biāo),而設(shè)計的一套相互協(xié)作的類。
感興趣的讀者可以閱讀《Design Patterns: Elements of Reusable Object-Oriented Software》,四位作者(Gang of Four)在書中列舉了業(yè)界聞名的23種設(shè)計模式。
這里先介紹我們框架要涉及的三種設(shè)計模式。
單例模式(singleton)
單例模式可以保證一個類只有一個對象實例, 常用在數(shù)據(jù)庫存取類,從而節(jié)省硬件資源的消耗。
這里,我們改寫上一章節(jié)的MySQL類
class MySQL extends DB{
private static $instance=null;
public static function getInstance(){
if(self::$instance==null){
self::$instance=new MySQL();
}
return self::$instance;
}
public function MySQL(){
/*Config*/
$this->IP='*';
$this->ServerID='*';
$this->ServerPassword='*';
$this->DataBaseName='*';
/*End of Config*/
$this->connection=mysqli_connect($this->IP,$this->ServerID,$this->ServerPassword,$this->DataBaseName);
if(!$this->connection){
die('Could not connect'.$this->connection);
}
mysqli_query($this->connection,'set names utf8');
}
public function Execute($sql){
return mysqli_query($this->connection,$sql);
}
public function Query($sql){
$result=mysqli_query($this->connection,$sql);
$arr=array();
while($row=mysqli_fetch_array($result)){
$arr[]=$row;
}
return $arr;
}
public function Close(){
mysqli_close($this->connection);
}
}
這里要注意的是,如果實例化一個MySQL類,我們不再寫
$db=new MySQL();
而是這樣:
$db=MySQL::getInstance();
因為只有g(shù)etInstance這個靜態(tài)函數(shù),才能保證只調(diào)用一次MySQL類的構(gòu)造函數(shù)。
單例模式是很常用的設(shè)計模式,這里不再贅述。
外觀模式(Facade)
因為命名空間的問題,外觀模式可以保證一個類的諸多方法看似是“一個類提供的”,這里我們先設(shè)計一個簡單的服務(wù)提供者類
class ServiceProvider{
public function Write($arg){
echo $arg;
}
}
這個類只有一個Write方法,就是把參數(shù)打印出來
然后定義一個Facade類
class Facade{
public static function getInstance($classname,$args){
return new $classname($args);
}
public static function getFacadeAccessor(){
//
}
public static function __callstatic($method,$args){
$instance=static::getInstance(static::getFacadeAccessor(),$args);
return call_user_func_array(array($instance,$method),$args);
}
}
要理解這個類,我們只要關(guān)注最后一個函數(shù),就是__callstatic魔術(shù)方法。這個方法就是Facade類型對象或者其子類在調(diào)用他自身沒有定義過的函數(shù)時,就會調(diào)用__callstatic方法,而這個方法最后調(diào)用了call_user_func_array函數(shù),就是把任務(wù)交給提供這項服務(wù)的類去完成,同時完成參數(shù)的傳遞。
我們再寫一個Facade子類
class MyFacade extends Facade{
public static function getFacadeAccessor(){
return ServiceProvider::class;
}
}
這里注意,子類實現(xiàn)了父類沒有具體實現(xiàn)的getFacadeAccessor方法,這個方法就是要告訴父類的__callstatic方法:“我作為Facade,代表的是什么哪個類,任務(wù)就由他來實現(xiàn)吧”,從語法上看,只是返回了一個表示類名的字符串。所以父類起初并不知道它的子類都代表著什么“服務(wù)提供者類”,只有當(dāng)子類的靜態(tài)函數(shù)被調(diào)用后,因為子類沒有該靜態(tài)函數(shù),所以父類的__callstatic方法被啟動了。
抽象工廠(Factory)
我對抽象工廠有一個粗俗的理解:“對象與字符串的對應(yīng)”,也就是用一個字符串就可以創(chuàng)造一個類的對象。這種做法主要用在兩種情況下是很方便的:
1.類名不穩(wěn)定,會在項目中頻繁修改
類名修改,很多時候并不是設(shè)計者的“命名潔癖”或者“命名強迫癥”導(dǎo)致的修改,而是在項目的不斷迭代,發(fā)覺這個類設(shè)計的不合理。如果這個類用的不頻繁,那么改個類名只要手工做一些小的修改即可,但是如果這個類通篇存在于代碼之中(假如是數(shù)據(jù)庫類),那修改工作量就大了,當(dāng)然,我們也可以對代碼文件使用“字符串替換”,但是假如一個PHP寫成的項目,PHP文件有幾十上百個,這也是不合理的事。
2.類的設(shè)計者并不是類的使用者
類的設(shè)計者和類的使用者不是同一個開發(fā)人員,那么記憶一個字符串或許比記憶一個類名要生動的多。我們都學(xué)過計算機(jī)網(wǎng)絡(luò)原理,都知道記憶一個域名要比記憶一個IP地址要生動的多,這就是DNS解決的問題。
因為抽象工廠很多教材都有涉及,不再贅述,本文將介紹一下目前非常流行的服務(wù)容器。
我們希望整個工程項目中,DB類,Session類,F(xiàn)ileSystem類“拿來即用”,不用每次繁瑣的初始化,比如寫$db=new DB(arg1,arg2);這類語句,也希望DB等類型的對象像一個“全局”變量一般,在整個程序運行期間,隨時可以調(diào)用。
服務(wù)容器可以讓調(diào)用DB等類型的程序員不用知道這個類太多的細(xì)節(jié),甚至可以用一個字符串的別名來創(chuàng)建這樣一個對象。
我們定義一個服務(wù)容器類
class Container{
public $bindings;
public function bind($abstract,$concrete){
$this->bindings[$abstract]=$concrete;
}
public function make($abstract,$parameters=[]){
return call_user_func_array($this->bindings[$abstract],$parameters);
}
}
可以把服務(wù)容器簡單的看成一個全局變量,bind方法就是用關(guān)聯(lián)數(shù)組把字符串和構(gòu)造函數(shù)做綁定。
至此,有了服務(wù)容器,我們的Model類就要做修改了
class Model implements IModel{
public static $table;
public static $container;
public static $db;
public function __construct(){
self::$container=new Container();
self::$container->bind('db',function(){
return MySQL::getInstance();
});
self::$db=self::$container->make('db',[]);
}
public static function get($id){
return self::where('id',$id);
}
public static function where($condition,$value){
$sql=sprintf("select * from %s where %s='%s'",self::$table,$condition,$value);
return self::$db->Query($sql);
}
public static function all(){
$sql=sprintf("select * from %s",self::$table);
return self::$db->Query($sql);
}
}
觀察上面代碼,我們同時用了單例模式和服務(wù)容器。
總結(jié):如果要做一個PHP框架,應(yīng)該要做好代碼的復(fù)用。設(shè)計模式一直是很多爭論的焦點,“究竟該不該使用設(shè)計模式?”,本文開始,我也努力回避“過于糾結(jié)這個問題”,我認(rèn)為,設(shè)計模式有其存在的價值,至少在具體項目中,確實在很多版本迭代中節(jié)省了工作量,提高工作效率,但是如果在一個小項目中為了“秀一下我會設(shè)計模式”而使用設(shè)計模式,就不合理了。
相關(guān)文章
PHP中date()日期函數(shù)有關(guān)參數(shù)整理
PHP中date()日期函數(shù)有關(guān)參數(shù)整理,需要的朋友可以參考下。2011-07-07
PHP數(shù)據(jù)庫操作之基于Mysqli的數(shù)據(jù)庫操作類庫
Mysqli 是什么,我這里也不進(jìn)行描述了。因為網(wǎng)上關(guān)于 Mysqli 的教程數(shù)不勝數(shù),我這里為大家介紹一款基于 Mysqli 的操作數(shù)據(jù)庫類(M.class.php)2014-04-04
PHP使用redis實現(xiàn)統(tǒng)計緩存mysql壓力的方法
這篇文章主要介紹了PHP使用redis實現(xiàn)統(tǒng)計緩存mysql壓力的方法,涉及PHP操作MySQL數(shù)據(jù)庫及使用Redis統(tǒng)計的相關(guān)技巧,需要的朋友可以參考下2015-11-11

