PHP常用的设计模式
1.单例模式
含义:
单例模式顾名思义,就是只有一个实例。作为对象的创建模式, 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
要点:
1. 一是某个类只能有一个实例;
2. 二是它必须自行创建这个实例;
3. 三是它必须自行向整个系统提供这个实例。
场景:
1. php的应用主要在于数据库应用, 一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式, 则可以避免大量的new 操作消耗的资源,还可以减少数据库连接这样就不容易出现 too many connections情况。
2. 如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现. 这个可以参看zend Framework的FrontController部分。
3. 在一次页面请求中, 便于进行调试, 因为所有的代码(例如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志,从而避免到处var_dump, echo。
示例:
class man { //保存例实例在此属性中 private static $_instance; //构造函数声明为private,防止直接创建对象 private function __construct() { echo '我被实例化了!'; } //单例方法 public static function get_instance() { var_dump(isset(self::$_instance)); if(!isset(self::$_instance)) { self::$_instance=new self(); } return self::$_instance; } //阻止用户复制对象实例 private function __clone() { trigger_error('Clone is not allow' ,E_USER_ERROR); } function test() { echo("test"); } } // 这个写法会出错,因为构造方法被声明为private //$test = new man; // 下面将得到Example类的单例对象 $test = man::get_instance(); $test = man::get_instance(); $test->test(); // 复制对象将导致一个E_USER_ERROR. //$test_clone = clone $test;
2.简单工厂模式
含义:
简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
要点:
1. 抽象基类:类中定义抽象一些方法,用以在子类中实现
2. 继承自抽象基类的子类:实现基类中的抽象方法
3. 工厂类:用以实例化所有相对应的子类
场景:
无
示例:
/** * 定义个抽象的类,让子类去继承实现它 */ abstract class Operation { // 抽象方法不能包含函数体 abstract public function getValue($num1, $num2); // 强烈要求子类必须实现该功能函数 } /** * 加法类 */ class OperationAdd extends Operation { public function getValue($num1, $num2) { return $num1 + $num2; } } /** * 减法类 */ class OperationSub extends Operation { public function getValue($num1, $num2) { return $num1 - $num2; } } /** * 乘法类 */ class OperationMul extends Operation { public function getValue($num1, $num2) { return $num1 * $num2; } } /** * 除法类 */ class OperationDiv extends Operation { public function getValue($num1, $num2) { try { if ($num2 == 0) { throw new Exception ( "除数不能为0" ); } else { return $num1 / $num2; } } catch ( Exception $e ) { echo "错误信息:" . $e->getMessage (); } } } // 通过采用面向对象的继承特性,我们可以很容易就能对原有程序进行扩展,比如:‘乘方’,‘开方’,‘对数’,‘三角函数’,‘统计’等,以还可以避免加载没有必要的代码。 // 如果我们现在需要增加一个求余的类,会非常的简单 // 我们只需要另外写一个类(该类继承虚拟基类),在类中完成相应的功能(比如:求乘方的运算),而且大大的降低了耦合度,方便日后的维护及扩展 /** * 求余类(remainder) */ class OperationRem extends Operation { public function getValue($num1, $num2) { return $num1 % $num12; } } // 现在还有一个问题未解决,就是如何让程序根据用户输入的操作符实例化相应的对象呢? // 解决办法:使用一个单独的类来实现实例化的过程,这个类就是工厂 /** * 工程类,主要用来创建对象 * 功能:根据输入的运算符号,工厂就能实例化出合适的对象 */ class Factory { public static function createObj($operate) { switch ($operate) { case '+' : return new OperationAdd (); break; case '-' : return new OperationSub (); break; case '*' : return new OperationSub (); break; case '/' : return new OperationDiv (); break; } } } $test = Factory::createObj ( '/' ); $result = $test->getValue ( 23, 0 ); echo $result;
3.观察者模式
含义:
观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:一个对象通过添加一个方法(该方法允许另一个对象,即观察者 注册自己)使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观察者使用该信息执行的操作与可观察的对象无关。结果是对象可以相互对话,而不必了解原因。
要点:
1. 定义2个接口:观察者(通知)接口、被观察者(主题)接口
2. 定义2个类,观察者对象实现观察者接口、主题类实现被观者接口
3. 主题类注册自己需要通知的观察者
4. 主题类某个业务逻辑发生时通知观察者对象,每个观察者执行自己的业务逻辑。
场景:
哈票以购票为核心业务(此模式不限于该业务),但围绕购票会产生不同的其他逻辑,如:
1、购票后记录文本日志
2、购票后记录数据库日志
3、购票后发送短信
4、购票送抵扣卷、兑换卷、积分
5、其他各类活动等
传统解决方案:
在购票逻辑等类内部增加相关代码,完成各种逻辑。
存在问题:
1、一旦某个业务逻辑发生改变,如购票业务中增加其他业务逻辑,需要修改购票核心文件、甚至购票流程。
2、日积月累后,文件冗长,导致后续维护困难。
存在问题原因主要是程序的"紧密耦合",使用观察模式将目前的业务逻辑优化成"松耦合",达到易维护、易修改的目的,
示例:
/** * 观察者接口(通知接口) */ interface ITicketObserver // 观察者接口 { function onBuyTicketOver($sender, $args); // 得到通知后调用的方法 } /** * 主题接口 */ interface ITicketObservable // 被观察对象接口 { function addObserver($observer); // 提供注册观察者方法 } //主题类实现 /** * 主题类(购票) * 实现主题接口(被观察者) */ class HipiaoBuy implements ITicketObservable { // 通知数组(观察者) private $_observers = array (); /** * 购票核心类,处理购票流程 */ public function buyTicket($ticket) { // TODO 购票逻辑 // 循环通知,调用其onBuyTicketOver实现不同业务逻辑 foreach ( $this->_observers as $obs ){ $obs->onBuyTicketOver ( $this, $ticket ); // $this 可用来获取主题类句柄,在通知中使用 } } // 添加通知 public function addObserver($observer){ $this->_observers [] = $observer; } } // 定义多个通知 // 短信日志通知 class HipiaoMSM implements ITicketObserver { public function onBuyTicketOver($sender, $ticket) { echo (date ( 'Y-m-d H:i:s' ) . " 短信日志记录:购票成功:$ticket<br>"); } } // 文本日志通知 class HipiaoTxt implements ITicketObserver { public function onBuyTicketOver($sender, $ticket) { echo (date ( 'Y-m-d H:i:s' ) . " 文本日志记录:购票成功:$ticket<br>"); } } // 抵扣卷赠送通知 class HipiaoDiKou implements ITicketObserver { public function onBuyTicketOver($sender, $ticket) { echo (date ( 'Y-m-d H:i:s' ) . " 赠送抵扣卷:购票成功:$ticket 赠送10元抵扣卷1张。<br>"); } } //用户购票 $buy = new HipiaoBuy (); //根据不同业务逻辑加入各种通知 $buy->addObserver ( new HipiaoMSM () ); $buy->addObserver ( new HipiaoTxt () ); $buy->addObserver ( new HipiaoDiKou () ); // 执行购票 $buy->buyTicket ( "一排一号" );
4.策略模式
含义:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
要点:
1. 一个接口,用于规范一系列策略,暴露统一的接口。
2. 抽象类,可有可无,主要用于提供辅助类。
3. 一系列策略类。
场景:
刘备要到江东娶老婆了,走之前诸葛亮给赵云三个锦囊妙计,说是按天机拆开能解决棘手问题。场景中出现三个要素:三个妙计(三种策略,具体策略类)、一个锦囊(环境类)、赵云(调用者)。
示例:
//一个接口,用于规范一系列策略,暴露统一的接口。 interface Strategy { public function operate(); } //策略一: 初到吴国 class BackDoor implements Strategy{ public function operate() { echo '找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备<br/>'; } } //策略二: 求吴国太开绿灯放行 class GivenGreenLight implements Strategy{ public function operate() { echo '求吴国太开个绿灯,放行<br/>'; } } //策略三: 孙夫人断后,挡住追兵 class BlackEnemy implements Strategy{ public function operate() { echo '孙夫人断后,挡住追兵<br/>'; } } class Context { /** * 策略对象 * @var Strategy */ private $strategy; function __construct(Strategy $_strategy){ $this->strategy = $_strategy; } public function operate() { $this->strategy->operate(); } } //调用者,这里就不封装成类了,直接使用 $context = null; //使用策略一 $context = new Context( new BackDoor() ); $context->operate(); //使用策略二 $context = new Context( new GivenGreenLight() ); $context->operate(); //使用策略三 $context = new Context( new BlackEnemy() ); $context->operate(); /** 依次输出: 找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备 求吴国太开个绿灯,放行 孙夫人断后,挡住追兵 **/