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();
/**
依次输出:
找乔国老帮忙,让吴国太给孙权施加压力,使孙权不能杀刘备
求吴国太开个绿灯,放行
孙夫人断后,挡住追兵
**/
