「七天自制PHP框架」第三天:PHP实现的设计模式

往期回顾:「七天自制PHP框架」第二天:模型与数据库,点击此处

原文地址:http://www.cnblogs.com/sweng/p/6624845.html,欢迎关注:编程老头

为什么要使用设计模式?

设计模式,我的理解是为了达到“可复用”这个目标,而设计的一套相互协作的类。

感兴趣的读者可以阅读《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框架,应该要做好代码的复用。设计模式一直是很多争论的焦点,“究竟该不该使用设计模式?”,本文开始,我也努力回避“过于纠结这个问题”,我认为,设计模式有其存在的价值,至少在具体项目中,确实在很多版本迭代中节省了工作量,提高工作效率,但是如果在一个小项目中为了“秀一下我会设计模式”而使用设计模式,就不合理了。

转载于:https://www.cnblogs.com/wuyuxin/p/7039722.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值