为什么要使用设计模式?
设计模式,我的理解是为了达到“可复用”这个目标,而设计的一套相互协作的类。
感兴趣的读者可以阅读《Design Patterns: Elements of Reusable Object-Oriented Software》,四位作者(Gang of Four)在书中列举了业界闻名的23种设计模式。
这里先介绍我们框架要涉及的三种设计模式。
单例模式(singleton)
单例模式可以保证一个类只有一个对象实例, 常用在数据库存取类,从而节省硬件资源的消耗。
这里,我们改写上一章节的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();
因为只有getInstance这个静态函数,才能保证只调用一次MySQL类的构造函数。
单例模式是很常用的设计模式,这里不再赘述。
外观模式(Facade)
因为命名空间的问题,外观模式可以保证一个类的诸多方法看似是“一个类提供的”,这里我们先设计一个简单的服务提供者类
class ServiceProvider{ public function Write($arg){ echo $arg; } }
这个类只有一个Write方法,就是把参数打印出来
然后定义一个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); } }
要理解这个类,我们只要关注最后一个函数,就是__callstatic魔术方法。这个方法就是Facade类型对象或者其子类在调用他自身没有定义过的函数时,就会调用__callstatic方法,而这个方法最后调用了call_user_func_array函数,就是把任务交给提供这项服务的类去完成,同时完成参数的传递。
我们再写一个Facade子类
class MyFacade extends Facade{ public static function getFacadeAccessor(){ return ServiceProvider::class; } }
这里注意,子类实现了父类没有具体实现的getFacadeAccessor方法,这个方法就是要告诉父类的__callstatic方法:“我作为Facade,代表的是什么哪个类,任务就由他来实现吧”,从语法上看,只是返回了一个表示类名的字符串。所以父类起初并不知道它的子类都代表着什么“服务提供者类”,只有当子类的静态函数被调用后,因为子类没有该静态函数,所以父类的__callstatic方法被启动了。
抽象工厂(Factory)
我对抽象工厂有一个粗俗的理解:“对象与字符串的对应”,也就是用一个字符串就可以创造一个类的对象。这种做法主要用在两种情况下是很方便的:
1.类名不稳定,会在项目中频繁修改
类名修改,很多时候并不是设计者的“命名洁癖”或者“命名强迫症”导致的修改,而是在项目的不断迭代,发觉这个类设计的不合理。如果这个类用的不频繁,那么改个类名只要手工做一些小的修改即可,但是如果这个类通篇存在于代码之中(假如是数据库类),那修改工作量就大了,当然,我们也可以对代码文件使用“字符串替换”,但是假如一个PHP写成的项目,PHP文件有几十上百个,这也是不合理的事。
2.类的设计者并不是类的使用者
类的设计者和类的使用者不是同一个开发人员,那么记忆一个字符串或许比记忆一个类名要生动的多。我们都学过计算机网络原理,都知道记忆一个域名要比记忆一个IP地址要生动的多,这就是DNS解决的问题。
因为抽象工厂很多教材都有涉及,不再赘述,本文将介绍一下目前非常流行的服务容器。