為什么要使用設計模式?
設計模式,我的理解是為了達到“可復用”這個目標,而設計的一套相互協作的類。
感興趣的讀者可以閱讀《design patterns: elements of reusable object-oriented software》,四位作者(gang of four)在書中列舉了業界聞名的23種設計模式。
這里先介紹我們框架要涉及的三種設計模式。
單例模式(singleton)
單例模式可以保證一個類只有一個對象實例, 常用在數據庫存取類,從而節省硬件資源的消耗。
這里,我們改寫上一章節的mysql類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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類,我們不再寫
1
|
$db = new mysql(); |
而是這樣:
1
|
$db =mysql::getinstance(); |
因為只有getinstance這個靜態函數,才能保證只調用一次mysql類的構造函數。
單例模式是很常用的設計模式,這里不再贅述。
外觀模式(facade)
因為命名空間的問題,外觀模式可以保證一個類的諸多方法看似是“一個類提供的”,這里我們先設計一個簡單的服務提供者類
1
2
3
4
5
|
class serviceprovider{ public function write( $arg ){ echo $arg ; } } |
這個類只有一個write方法,就是把參數打印出來
然后定義一個facade類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 ); } } |
要理解這個類,我們只要關注最后一個函數,就是__callstatic魔術方法。這個方法就是facade類型對象或者其子類在調用他自身沒有定義過的函數時,就會調用__callstatic方法,而這個方法最后調用了call_user_func_array函數,就是把任務交給提供這項服務的類去完成,同時完成參數的傳遞。
我們再寫一個facade子類
1
2
3
4
5
|
class myfacade extends facade{ public static function getfacadeaccessor(){ return serviceprovider:: class ; } } |
這里注意,子類實現了父類沒有具體實現的getfacadeaccessor方法,這個方法就是要告訴父類的__callstatic方法:“我作為facade,代表的是什么哪個類,任務就由他來實現吧”,從語法上看,只是返回了一個表示類名的字符串。所以父類起初并不知道它的子類都代表著什么“服務提供者類”,只有當子類的靜態函數被調用后,因為子類沒有該靜態函數,所以父類的__callstatic方法被啟動了。
抽象工廠(factory)
我對抽象工廠有一個粗俗的理解:“對象與字符串的對應”,也就是用一個字符串就可以創造一個類的對象。這種做法主要用在兩種情況下是很方便的:
1.類名不穩定,會在項目中頻繁修改
類名修改,很多時候并不是設計者的“命名潔癖”或者“命名強迫癥”導致的修改,而是在項目的不斷迭代,發覺這個類設計的不合理。如果這個類用的不頻繁,那么改個類名只要手工做一些小的修改即可,但是如果這個類通篇存在于代碼之中(假如是數據庫類),那修改工作量就大了,當然,我們也可以對代碼文件使用“字符串替換”,但是假如一個php寫成的項目,php文件有幾十上百個,這也是不合理的事。
2.類的設計者并不是類的使用者
類的設計者和類的使用者不是同一個開發人員,那么記憶一個字符串或許比記憶一個類名要生動的多。我們都學過計算機網絡原理,都知道記憶一個域名要比記憶一個ip地址要生動的多,這就是dns解決的問題。
因為抽象工廠很多教材都有涉及,不再贅述,本文將介紹一下目前非常流行的服務容器。
我們希望整個工程項目中,db類,session類,filesystem類“拿來即用”,不用每次繁瑣的初始化,比如寫$db=new db(arg1,arg2);這類語句,也希望db等類型的對象像一個“全局”變量一般,在整個程序運行期間,隨時可以調用。
服務容器可以讓調用db等類型的程序員不用知道這個類太多的細節,甚至可以用一個字符串的別名來創建這樣一個對象。
我們定義一個服務容器類
1
2
3
4
5
6
7
8
9
|
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 ); } } |
可以把服務容器簡單的看成一個全局變量,bind方法就是用關聯數組把字符串和構造函數做綁定。
至此,有了服務容器,我們的model類就要做修改了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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 ); } } |
觀察上面代碼,我們同時用了單例模式和服務容器。
總結:如果要做一個php框架,應該要做好代碼的復用。設計模式一直是很多爭論的焦點,“究竟該不該使用設計模式?”,本文開始,我也努力回避“過于糾結這個問題”,我認為,設計模式有其存在的價值,至少在具體項目中,確實在很多版本迭代中節省了工作量,提高工作效率,但是如果在一個小項目中為了“秀一下我會設計模式”而使用設計模式,就不合理了。
原文鏈接:http://www.cnblogs.com/sweng/p/6666008.html