8

1、单例模式

目的:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用场景:数据库连接、缓存操作、分布式存储。

/**
    * 设计模式之单例模式
    * $_instance必须声明为静态的私有变量
    * 构造函数和析构函数必须声明为私有,防止外部程序new
    * 类从而失去单例模式的意义
    * getInstance()方法必须设置为公有的,必须调用此方法
    * 以返回实例的一个引用
    * ::操作符只能访问静态变量和静态函数
    * new对象都会消耗内存
    * 使用场景:最常用的地方是数据库连接。
    * 使用单例模式生成一个对象后,
    * 该对象可以被其它众多对象所使用。
*/
class Danli {

//保存类实例的静态成员变量
    private static $_instance;

//private标记的构造方法,私有化构造函数,不允许外部创建实例
    private function __construct(){
        echo 'This is a Constructed method;';
    }

//创建__clone方法防止对象被复制克隆
    public function __clone(){
        trigger_error('Clone is not allow!',E_USER_ERROR);
    }

//单例方法,用于访问实例的公共的静态方法
    public static function getInstance(){
        if(!(self::$_instance instanceof self)){
            self::$_instance = new self;
        }
        return self::$_instance;
    }

    public function test(){
        echo '调用方法成功';
    }

}

//用new实例化private标记构造函数的类会报错
//$danli = new Danli();

//正确方法,用双冒号::操作符访问静态方法获取实例
$danli = Danli::getInstance();
$danli->test();
echo '<br />';
$danli = Danli::getInstance();//是否打印This is a Constructed method;判断是否实例化对象
$danli->test();

//复制(克隆)对象将导致一个E_USER_ERROR
$danli_clone = clone $danli;

运行结果:

This is a Constructed method;调用方法成功
调用方法成功
( ! ) Fatal error: Clone is not allow! in C:\wamp\www\test\singleton.php on line 34
Call Stack
#    Time    Memory    Function    Location
1    0.0007    139200    {main}( )    ..\singleton.php:0
2    0.0007    139416    Danli->__clone( )    ..\singleton.php:62
3    0.0007    139544    trigger_error ( )    ..\singleton.php:34

2、工厂模式

使用工厂模式的目的或目标?

工厂模式的最大优点在于创建对象上面,就是把创建对象的过程封装起来,这样随时可以产生一个新的对象。
减少代码进行复制粘帖,耦合关系重,牵一发动其他部分代码。

通俗的说,以前创建一个对象要使用new,现在把这个过程封装起来了。
假设不使用工厂模式:那么很多地方调用类a,代码就会这样子创建一个实例:new a(),假设某天需要把a类的名称修改,意味着很多调用的代码都要修改。

工厂模式的优点就在创建对象上。建立一个工厂(一个函数或一个类方法)来制造新的对象,它的任务就是把对象的创建过程都封装起来。
创建对象不是使用new的形式了。而是定义一个方法,用于创建对象实例。

每个类可能会需要连接数据库。那么就将连接数据库封装在一个类中。以后在其他类中通过类名:

为什么引入抽象的概念?
想一想,在现实生活中,当我们无法确定某个具体的东西的时候,往往把一类东西归于抽象类别。
工厂方法:
比如你的工厂叫做“香烟工厂”,那么可以有“七匹狼工厂”“中华工厂”等,但是,这个工厂只生厂一种商品:香烟;
抽象工厂:无法描述它到底生产什么产品,它生产很多类型的产品(所以抽象工厂就会生成子工厂)。
你的工厂是综合型的,是生产“一系列”产品,而不是“一个”,比如:生产“香烟”,还有“啤酒”等。然后它也可以有派生出来的具体的工厂,但这些工厂都是生产这一系列产品,只是可能因为地域不一样,为了适应当地人口味,味道也不太一样。
工厂模式:理解成只生成一种产品的工厂。比如生产香烟的。
工厂方法:工厂的一种产品生产线 。比如键盘的生成过程。

别人会反驳:吃饱了没事干,一定要修改类名称呢?这个说不定。一般都不会去修改类名称。

其实工厂模式有很多变体,抓住精髓才是关键:只要是可以根据不同的参数生成不同的类实例,那么就符合工厂模式的设计思想

这样子让我联想到框架中经常会有负责生成具体类实例的方法供调用。

由于前面使用过phpcms,用phpcms的来帮助理解,更加好,如下:

pc_base:load_app_class("order"');//参数名称就是类名称。将会生成得到order这个实例。传递不同的参数得到不同的类实例,这个就符合工厂模式。

pc_base:load_app_class("comment"');//生成一个comment类实例

//当然load_app_class这个方法里面还会结合了单件模式的思想。避免调用n次,就重复创建n个相同的实例

工厂模式我想到的一个典型的应用就是:php可能要链接mysql,也可能要链接sqlserver,还有其他什么数据库。那么做一个抽象的数据库类,

这个类就是一个工厂类,专门负责产生不同的对象。

这样子做很方便扩展。我们在直接链接数据库的时候,不是使用代码new Mysql($host,$username,$password,$dbname)的形式

而可以动态生成一个连接数据库的实例。可以是mysql,也可以是连接oracle的。

class DbFactory
{

function static factory($db_class_name)
{

        $db_class_name = strtolower($db_class_name);

        if (include_once 'Drivers/' . $db_class_name . '.php') {

     
            $classname = 'Driver_' . $db_class_name;
            return new $db_class_name;
        } else {
            throw new Exception ('对应的数据库类没找到');
        } 


}




}



DbFactory::factory("mysql");

DbFactory::factory("oracle");

在thinkphp框架中也有对应的实现:

Db.class.php就是一个工厂类(也可以叫做数据库中间层,之所以叫做中间层,是因为可以操作mysql、oracle等各数据库。而这个类就是中间层作用,屏蔽掉具体的实现。让程序员可以不改动原来的查询代码。中间层来对接mysql、oracle等数据库。

Db.class.php中有个factory()方法来创建不同的数据库实例

    public function factory($db_config='') {
        // 读取数据库配置
        $db_config = $this->parseConfig($db_config);
        if(empty($db_config['dbms']))
            throw_exception(L('_NO_DB_CONFIG_'));
        // 数据库类型
        $this->dbType = ucwords(strtolower($db_config['dbms']));
        $class = 'Db'. $this->dbType;
        if(is_file(CORE_PATH.'Driver/Db/'.$class.'.class.php')) {
            // 内置驱动
            $path = CORE_PATH;
        }else{ // 扩展驱动
            $path = EXTEND_PATH;
        }
        // 检查驱动类
        if(require_cache($path.'Driver/Db/'.$class.'.class.php')) {
            $db = new $class($db_config);
            // 获取当前的数据库类型
            if( 'pdo' != strtolower($db_config['dbms']) )
                $db->dbType = strtoupper($this->dbType);
            else
                $db->dbType = $this->_getDsnType($db_config['dsn']);
            if(APP_DEBUG)  $db->debug    = true;
        }else {
            // 类没有定义
            throw_exception(L('_NOT_SUPPORT_DB_').': ' . $db_config['dbms']);
        }
        return $db;
    }

还有做支付接口的时候,未来可能对应不同的支付网关:支付宝、财付通、网银在线等。方便未来扩展,设计成工厂模式。定一个专门生产网关接口的工厂,抽象出来,做成接口形式,让所有的子类都要实现它的接口。以后加一个支付方式,要使用哪一种支付方式,改变一下参数即可。

书籍<php权威编程>(英文名称为PHP 5 Power Programming)也提到一个工厂模式的例子,学到一招:在为用户注册的时候,分为很多种角色的用户。比如册用户,匿名用户、管理员用户等。完全使用可以使用工厂的思想来实现,代码也容易维护,为每种角色可以生成操作的类。

定义以下几个类:

UserFactory 用户工厂类,负责生成不同的用户类
User:用户类的基类,所有用户类都是继承这个类
不同角色的类:注册用户类、匿名用户类、管理员用户类
参考文档:php设计模式总结-工厂模式

/**
 *简单工厂模式与工厂方法模式比较。
 *简单工厂又叫静态工厂方法模式,这样理解可以确定,简单工厂模式是通过一个静态方法创建对象的。
 */
interface  people {
    function  jiehun();
}
class man implements people{
    function jiehun() {
        echo '送玫瑰,送戒指!<br>';
    }
}

class women implements people {
    function jiehun() {
        echo '穿婚纱!<br>';
    }
}

class SimpleFactoty {
    // 简单工厂里的静态方法
    static function createMan() {
        return new     man;
    }
    static function createWomen() {
        return new     women;
    }

}

$man = SimpleFactoty::createMan();
$man->jiehun();
$man = SimpleFactoty::createWomen();
$man->jiehun();

运行结果:

送玫瑰,送戒指!
穿婚纱!
/*
 *工厂方法模式:
 *定义一个创建对象的接口,让子类决定哪个类实例化。 他可以解决简单工厂模式中的封闭开放原则问题。<www.phpddt.com整理>
 */
interface  people {
    function  jiehun();
}
class man implements people{
    function jiehun() {
        echo '送玫瑰,送戒指!<br>';
    }
}

class women implements people {
    function jiehun() {
        echo '穿婚纱!<br>';
    }
}

interface  createMan {  // 注意了,这里是简单工厂本质区别所在,将对象的创建抽象成一个接口。
    function create();

}
class FactoryMan implements createMan{
    function create() {
        return  new man;
    }
}
class FactoryWomen implements createMan {
    function create() {
        return new women;
    }
}

class  Client {
    // 简单工厂里的静态方法
    function test() {
        $Factory =  new  FactoryMan;
        $man = $Factory->create();
        $man->jiehun();

        $Factory =  new  FactoryWomen;
        $man = $Factory->create();
        $man->jiehun();
    }
}
$f = new Client;
$f->test();

运行结果:

送玫瑰,送戒指!
穿婚纱!

/*
抽象工厂:提供一个创建一系列相关或相互依赖对象的接口。
注意:这里和工厂方法的区别是:一系列,而工厂方法则是一个。
那么,我们是否就可以想到在接口create里再增加创建“一系列”对象的方法呢?
*/
interface  people {
    function  jiehun();
}
class Oman implements people{
    function jiehun() {
        echo '美女,我送你玫瑰和戒指!<br>';
    }
}
class Iman implements people{
    function jiehun() {
        echo '我偷偷喜欢你<br>';
    }
}

class Owomen implements people {
    function jiehun() {
        echo '我要穿婚纱!<br>';
    }
}

class Iwomen implements people {
    function jiehun() {
        echo '我好害羞哦!!<br>';
    }
}

interface  createMan {  // 注意了,这里是本质区别所在,将对象的创建抽象成一个接口。
    function createOpen(); //分为 内敛的和外向的
    function createIntro(); //内向
}
class FactoryMan implements createMan{
    function createOpen() {
        return  new  Oman;
    }
    function createIntro() {
        return  new Iman;
    }
}
class FactoryWomen implements createMan {
    function createOpen() {
        return  new  Owomen;
    }
    function createIntro() {
        return  new Iwomen;
    }
}

class  Client {
    // 简单工厂里的静态方法
    function test() {
        $Factory =  new  FactoryMan;
        $man = $Factory->createOpen();
        $man->jiehun();

        $man = $Factory->createIntro();
        $man->jiehun();


        $Factory =  new  FactoryWomen;
        $man = $Factory->createOpen();
        $man->jiehun();

        $man = $Factory->createIntro();
        $man->jiehun();

    }
}

$f = new Client;
$f->test();

运行结果:

美女,我送你玫瑰和戒指!
我偷偷喜欢你
我要穿婚纱!
我好害羞哦!!

区别:
简单工厂模式:用来生产同一等级结构中的任意产品。对与增加新的产品,无能为力

工厂模式 :用来生产同一等级结构中的固定产品。(支持增加任意产品)
抽象工厂 :用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)

以上三种工厂 方法在等级结构和产品族这两个方向上的支持程度不同。所以要根据情况考虑应该使用哪种方法

适用范围:
简单工厂模式:

工厂类负责创建的对象较少,客户只知道传入工厂类的参数,对于如何创建对象不关心。

工厂方法模式:

当一个类不知道它所必须创建对象的类或一个类希望由子类来指定它所创建的对象时,当类将创建对象的职责委托给多个帮助子类中得某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候,可以使用工厂方法模式。

抽象工厂模式:

一个系统不应当依赖于产品类实例何如被创建,组合和表达的细节,这对于所有形态的工厂模式都是重要的。这个系统有多于一个的产品族,而系统只消费其 中某一产品族。同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。系统提供一个产品类的库,所有的产品以同样的接口出现,从 而使客户端不依赖于实现。

无论是简单工厂模式、工厂模式还是抽象工厂模式,它们本质上都是将不变的部分提取出来,将可变的部分留作接口,以达到最大程度上的复用。究竟用哪种设计模式更适合,这要根据具体的业务需求来决定。

参考文档:PHP简单工厂模式、工厂方法模式和抽象工厂模式比较
参考文档: 设计模式——简单工厂模式—工厂方法模式—抽象工厂模式(比较)

3、观察者模式

定义:当一个对象状态发生改变时,依赖他的对象全部得到通知
优点:低耦合、非侵入式

观察者模式为您提供了避免组件之间紧密耦合的另一种方法。该模式非常简单:一个对象通过添加一个方法(该方法允许另一个对象,即观察者注册自己)使本身变得可观察。当可观察的对象更改时,它会将消息发送到已注册的观察者。这些观察者使用该信息执行的操作与可观察的对象无关。结果是对象可以相互对话,而不必了解原因。

一个简单的示例:当听众在收听电台时(即电台加入一个新听众),它将发送出一条提示消息,通过发送消息的日志观察者可以观察这些消息。

总结: 当新对象要填入的时候,只需要在主题(又叫可观察者)中进行注册(注册方式很多,你也可以在构造的时候,或者框架访问的接口中进行注册),然后实现代码直接在新对象的接口中进行。这降低了主题对象和观察者对象的耦合度。

clipboard.png

/**
 * 观察者模式
 */
class Paper{ /* 主题    */
    private $_observers = array();

    public function register($sub){ /*  注册观察者 */
        $this->_observers[] = $sub;
    }


    public function trigger(){  /*  外部统一访问    */
        if(!empty($this->_observers)){
            foreach($this->_observers as $observer){
                $observer->update();
            }
        }
    }
}

/**
 * 观察者要实现的接口
 */
interface Observerable{
    public function update();
}

class Subscriber implements Observerable{
    public function update(){
        echo "Callback\n";
    }
}
class Subscriber1 implements Observerable{
    public function update(){
        echo "Callback1\n";
    }
}
class Subscriber2 implements Observerable{
    public function update(){
        echo "Callback2\n";
    }
}


/*  测试    */
$paper = new Paper();
$paper->register(new Subscriber());
$paper->register(new Subscriber1());
$paper->register(new Subscriber2());
$paper->trigger();

运行结果:Callback Callback1 Callback2

more:注册/移除观察者
如下代码:

<?php
// 观察者接口
interface IObserver {
    function onListen($sender, $args);
    function getName();
}
// 可被观察接口
interface IObservable {
    function addObserver($observer);
    function removeObserver($observer_name);
}
// 观察者类
abstract class Observer implements IObserver {
    protected $name;
    public function getName() {
        return $this->name;
    }
}
// 可被观察类
class Observable implements IObservable {
    protected $observers = array();
    public function addObserver($observer) {
        if (!($observer instanceof IObserver)) {
            return;
        }
        $this->observers[] = $observer;
    }
    public function removeObserver($observer_name) {
        foreach ($this->observers as $index => $observer) {
            if ($observer->getName() === $observer_name) {
                array_splice($this->observers, $index, 1);
                return;
            }
        }
    }
}
// 模拟一个可以被观察的类:RadioStation
class RadioStation extends Observable {
    public function addListener($listener) {
        foreach ($this->observers as $observer) {
            $observer->onListen($this, $listener);
        }
    }
}
// 模拟一个观察者类
class RadioStationLogger extends Observer {
    protected $name = 'logger';
    public function onListen($sender, $args) {
        echo $args, ' join the radiostation.<br/>';
    }
}
// 模拟另外一个观察者类
class OtherObserver extends Observer {
    protected $name = 'other';
    public function onListen($sender, $args) {
        echo 'other observer..<br/>';
    }
}
$rs = new RadioStation();
// 注入观察者
$rs->addObserver(new RadioStationLogger());
$rs->addObserver(new OtherObserver());
// 移除观察者
$rs->removeObserver('other');
// 可以看到观察到的信息
$rs->addListener('cctv');
?>

改进:加入魔术常量__CLASS__,通过类名移除观察者,更灵活,注意__CLASS__只能在注册类里面添加,不能在抽象类添加。

// 观察者接口
interface IObserver {
    function onListen($sender, $args);
    function getName();
}
// 可被观察接口
interface IObservable {
    function addObserver($observer);
    function removeObserver($observer_name);
}
// 观察者类
abstract class Observer implements IObserver {
    protected $name ;//这个地方不能name=__CLASS__,这样类名就是Observer,移除观察者时会失败
    public function getName() {
        return $this->name;
//        return __CLASS__;
    }
}
// 可被观察类
class Observable implements IObservable {
    protected $observers = array();
    public function addObserver($observer) {
        if (!($observer instanceof IObserver)) {
            return;
        }
        $this->observers[] = $observer;
    }
    public function removeObserver($observer_name) {
        foreach ($this->observers as $index => $observer) {
            if ($observer->getName() === $observer_name) {
                array_splice($this->observers, $index, 1);
                return;
            }
        }
    }
}
// 模拟一个可以被观察的类:RadioStation
class RadioStation extends Observable {
    public function addListener($listener) {
        foreach ($this->observers as $observer) {
            $observer->onListen($this, $listener);
        }
    }
}
// 模拟一个观察者类
class RadioStationLogger extends Observer {
//    protected $name = 'logger';
    protected $name = __CLASS__;
    public function onListen($sender, $args) {
        echo $args, ' join the radiostation.<br/>';
    }
}
// 模拟另外一个观察者类
class OtherObserver extends Observer {
//    protected $name = 'other';
    protected $name = __CLASS__;
    public function onListen($sender, $args) {
        echo $this->name.'other observer..<br/>';
    }
}
$rs = new RadioStation();
// 注入观察者
$rs->addObserver(new RadioStationLogger());
$rs->addObserver(new OtherObserver());
// 移除观察者
$rs->removeObserver('OtherObserver');
//$rs->removeObserver('Observer');
// 可以看到观察到的信息
$rs->addListener('cctv');

Bohr
6.5k 声望3.3k 粉丝