这里有新鲜出炉的 PHP 设计模式,程序狗速度看过来!
PHP(外文名: Hypertext Preprocessor,中文名:"超文本预处理器")是一种通用开源脚本语言。语法吸收了 C 语言、Java 和 Perl 的特点,入门门槛较低,易于学习,使用广泛,主要适用于 web 开发领域。PHP 的文件后缀名为 php。
本文是自制 PHP 框架的第三篇,我们主要来讲解下 PHP 的设计模式的使用,本文我们一起来学习 PHP 中最常用的三种设计模式:单例设计模式、工厂设计模式和观察者设计模式。
为什么要使用设计模式?
设计模式,我的理解是为了达到 "可复用" 这个目标,而设计的一套相互协作的类。
感兴趣的读者可以阅读《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 解决的问题。
因为抽象工厂很多教材都有涉及,不再赘述,本文将介绍一下目前非常流行的服务容器。
我们希望整个工程项目中,DB 类,Session 类,FileSystem 类 "拿来即用",不用每次繁琐的初始化,比如写 $db=new DB(arg1,arg2); 这类语句,也希望 DB 等类型的对象像一个 "全局" 变量一般,在整个程序运行期间,随时可以调用。
服务容器可以让调用 DB 等类型的程序员不用知道这个类太多的细节,甚至可以用一个字符串的别名来创建这样一个对象。
我们定义一个服务容器类
- 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 类就要做修改了
- 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.phperz.com/article/17/0809/339913.html