zoukankan      html  css  js  c++  java
  • php 设计模式

    1.单例模式

    单例模式顾名思义,就是只有一个实例。作为对象的创建模式, 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    单例模式的要点有三个:

    1. 一是某个类只能有一个实例;
    2. 二是它必须自行创建这个实例;
    3. 三是它必须自行向整个系统提供这个实例。
    为什么要使用PHP单例模式
    1. 1. php的应用主要在于数据库应用, 一个应用中会存在大量的数据库操作, 在使用面向对象的方式开发时, 如果使用单例模式, 则可以避免大量的new 操作消耗的资源,还可以减少数据库连接这样就不容易出现 too many connections情况。
    2. 2. 如果系统中需要有一个类来全局控制某些配置信息, 那么使用单例模式可以很方便的实现. 这个可以参看zend Framework的FrontController部分。
    3. 3. 在一次页面请求中, 便于进行调试, 因为所有的代码(例如数据库操作类db)都集中在一个类中, 我们可以在类中设置钩子, 输出日志,从而避免到处var_dump, echo。

     例子:

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

        //阻止用户复制对象实例
        // __clone()函数解释 http://www.androiddev.net/php-object-clone-copy-constructor/
        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.简单工厂模式
    • ①抽象基类:类中定义抽象一些方法,用以在子类中实现
    • ②继承自抽象基类的子类:实现基类中的抽象方法
    • ③工厂类:用以实例化所有相对应的子类

     

    复制代码
        
        /**
         * 
         * 定义个抽象的类,让子类去继承实现它
         *
         */
         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;
    复制代码

    其他关于关于此模式的笔记

    工厂模式:
    以交通工具为例子:要求请既可以定制交通工具,又可以定制交通工具生产的过程
    1>定制交通工具
        1.定义一个接口,里面包含交工工具的方法(启动 运行 停止)

        2.让飞机,汽车等类去实现他们
    2> 定制工厂(通上类似)
        1.定义一个接口,里面包含交工工具的制造方法(启动 运行 停止)

        2.分别写制造飞机,汽车的工厂类去继承实现这个接口

     原文地址:http://bbs.phpchina.com/thread-242243-1-1.html

    3.观察者模式

     观 察者模式属于行为模式,是定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依 赖于它的对象都得到通知并自动刷新。它完美的将观察者对象和被观察者对象分离。可以在独立的对象(主体)中维护一个对主体感兴趣的依赖项(观察器)列表。 让所有观察器各自实现公共的 Observer 接口,以取消主体和依赖性对象之间的直接依赖关系。

    用到了 spl (standard php library)

     
    class MyObserver1 implements SplObserver {
        public function update(SplSubject $subject) {
            echo __CLASS__ . ' - ' . $subject->getName();
        }
    }

    class MyObserver2 implements SplObserver {
        public function update(SplSubject $subject) {
            echo __CLASS__ . ' - ' . $subject->getName();
        }
    }

    class MySubject implements SplSubject {
        private $_observers;
        private $_name;

        public function __construct($name) {
            $this->_observers = new SplObjectStorage();
            $this->_name = $name;
        }

        public function attach(SplObserver $observer) {
            $this->_observers->attach($observer);
        }

        public function detach(SplObserver $observer) {
            $this->_observers->detach($observer);
        }

        public function notify() {
            foreach ($this->_observers as $observer) {
                $observer->update($this);
            }
        }

        public function getName() {
            return $this->_name;
        }
    }

    $observer1 = new MyObserver1();
    $observer2 = new MyObserver2();

    $subject = new MySubject("test");

    $subject->attach($observer1);
    $subject->attach($observer2);

     参考原文:http://www.php.net/manual/zh/class.splsubject.php

    3.1观察者模式

    利用 SPL 快速实现 Observer 设计模式

    本文介绍并演示了如何通过 SPL 中的 SplSubject 和 SplObserver 接口以及 SplObjectStorage 类快速实现 Observer 设计模式。并给出如何通过一个小技巧使观察者(Observer)在更新时可以使用由目标(Subject)传递的数据参数,即目标使用推模型(push model)来通知观察者。

    什么是 SPL

    SPL(Standard PHP Library)即标准 PHP 库,是 PHP 5 在面向对象上能力提升的真实写照,它由一系列内置的类、接口和函数构成。SPL 通过加入集合,迭代器,新的异常类型,文件和数据处理类等提升了 PHP 语言的生产力。它还提供了一些十分有用的特性,如本文要介绍的内置 Observer 设计模式。

    本文介绍如何通过使用 SPL 提供的 SplSubjectSplObserver接口以及 SplObjectStorage类,快速实现 Observer 设计模式。

    SPL 在大多数 PHP 5 系统上都是默认开启的,尽管如此,由于 SPL 的功能在 PHP 5.2 版本发生了引人注目的改进,所以建议读者在实践本文内容时,使用不低于 PHP 5.2 的版本。

    SplSubject 和 SplObserver 接口

    Observer 设计模式定义了对象间的一种一对多的依赖关系,当被观察的对象发生改变时,所有依赖于它的对象都会得到通知并被自动更新,而且被观察的对象和观察者之间是 松耦合的。在该模式中,有目标(Subject)和观察者(Observer)两种角色。目标角色是被观察的对象,持有并控制着某种状态,可以被任意多个 观察者作为观察的目标,SPL 中使用 SplSubject接口规范了该角色的行为:

    表 1. SplSubject 接口中的方法
    方法声明描述
    abstract public void attach ( SplObserver $observer ) 添加(注册)一个观察者
    abstract public void detach ( SplObserver $observer ) 删除一个观察者
    abstract public void notify ( void ) 当状态发生改变时,通知所有观察者

    观察者角色是在目标发生改变时,需要得到通知的对象。SPL 中用 SplObserver接口规范了该角色的行为:

    表 2. SplObserver 中的方法
    方法声明描述
    abstract public void update ( SplSubject $subject ) 在目标发生改变时接收目标发送的通知;当关注的目标调用其 notify()时被调用


    该设计模式的核心思想是,SplSubject对象会在其状态改变时调用 notify()方法,一旦这个方法被调用,任何先前通过 attach()方法注册上来的 SplObserver对象都会以调用其 update()方法的方式被更新。

    为什么使用 SplObjectStorage 类(存储唯一的对象的数组)

    SplObjectStorage类 实现了以对象为键的映射(map)或对象的集合(如果忽略作为键的对象所对应的数据)这种数据结构。这个类的实例很像一个数组,但是它所存放的对象都是唯 一的。这个特点就为快速实现 Observer 设计模式贡献了不少力量,因为我们不希望同一个观察者被注册多次。该类的另一个特点是,可以直接从中删除指定的对象,而不需要遍历或搜索整个集合。

    SplObjectStorage类的实例之所以能够只存储唯一的对象,是因为其 SplObjectStorage::attach()方法的实现中先判断了指定的对象是否已经被存储:

     清单 1. SplObjectStorage::attach() 方法的部分源代码

    复制代码
     function attach($obj, $inf = NULL) 
     { 
        if (is_object($obj) && !$this->contains($obj)) 
        { 
            $this->storage[] = array($obj, $inf); 
        } 
     }
    复制代码

    模拟案例

    下面我们通过一个模拟案例来演示 SPL 在实现 Observer 设计模式上的威力。该案例模拟了一个网站的用户管理模块,该模块包括 3 个主要功能:

    • 新增 1 个用户
    • 把指定用户的密码变更为他所指定的新密码
    • 在用户忘记密码时重置其密码

    每当这些功能完成后,都需要将密码告知用户。除了传统的向用户发送 Email 这种手段外,我们还需要向用户的手机发送短信,让他们更加方便地知道密码是什么。假设我们的网站还有一套站内的消息系统,我们称之为小纸条,在用户变更或 重置密码后,向他们发送小纸条会令他们高兴的。

    经过分析,该案例适合使用 Observer 设计模式解决,因为将密码告知用户的多种手段与用户密码的改变——无论是从无到有,用户主动变更,还是系统重置——形成了多对一的关系。

    我 们决定定义一个 User 类表示用户,实现需求中的 3 个功能。该类就是 Observer 设计模式中的目标(Subject)角色。我们还需要一组类,实现利用各种手段向用户发送新密码的功能,这些类就充当了 Observer 设计模式中的观察者(Observer)角色。

    经过简单地分析后,我们画出 UML 类图:

    图 1. 模拟案例的 UML 类图

    图 1. 模拟案例的 UML 类图

    根据 UML 类图,首先,定义 1 个名为 User 的类模拟案例中的用户。尽管实际网站中的用户要有更多的属性,特别是通常需要用 ID 来标识每个用户,但是我们为了突出本文的主题,只保留了案例所需的属性。

     User.php 方法的部分源代码

    复制代码
     class User implements SplSubject {

        private $email;
        private $username;
        private $mobile;
        private $password;
        /**
         * @var SplObjectStorage
         */
        private $observers = NULL;

        public function __construct($email, $username, $mobile, $password) {
            $this->email = $email;
            $this->username = $username;
            $this->mobile = $mobile;
            $this->password = $password;

            $this->observers = new SplObjectStorage();//存储唯一对象的数组
        }

        public function attach(SplObserver $observer) {
            $this->observers->attach($observer);
        }

        public function detach(SplObserver $observer) {
            $this->observers->detach($observer);
        }

        public function notify() {
            $userInfo = array(
                'username' => $this->username,
                'password' => $this->password,
                'email' => $this->email,
                'mobile' => $this->mobile,
            );
            foreach ($this->observers as $observer) {
                $observer->update($this, $userInfo);
            }
        }

        public function create() {
            echo __METHOD__.'<br/>';//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>
            $this->notify();
        }

        public function changePassword($newPassword) {
            echo __METHOD__.'<br/>';//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>
            $this->password = $newPassword;
            $this->notify();
        }

        public function resetPassword() {
            echo __METHOD__.'<br/>';
            $this->password = mt_rand(100000, 999999);
            $this->notify();
        }

    }
    复制代码

    User 类要想充当目标角色,就需要实现 SplSubject接口,而按照实现接口的法则,attach()detach()notify()就必须被实现。请注意,由于在 SplSubject接口中,attach() 和detach() 的参数都使用了类型提示(type hinting),在实现这两个方法时,也不能省略参数前面的类型。我们还使用了 $observers实例属性保存一个 SplObjectStorage对象,用来存放所有注册上来的观察者。

    的确,一个数组就能解决问题,但是很快就可以发现,使用了 SplObjectStorage之后删除一个观察者实现起来是多么简单,直接委托给 SplObjectStorage对象!是的,不需要再使用最原始的 for语句遍历观察者数组或者使用 array_search函数,1 行搞定。

    接下来分别定义充当观察者角色的 3 个信息发送类。为了简单,我们只是通过输出文本来假装发送信息。可即使是假装,依然需要知道用户的信息。可看看 SplObserver接口 update()方法的签名,多么令人沮丧,它无法接受目标角色通过调用其 notify() 方法发送通告时给出的参数。如果你试图在重写 update()方法时加上第 2 个参数,会得到一个类似

    Fatal error: Declaration of EmailSender::update() must be compatible with that of SplObserver::update() 的错误而使代码执行终止。

    其实,当目标所持有的状态(在本例中是用户的密码)更新时,如何通知观察者有两种方法。“拉”的方法和“推”的方法。SPL 使用的是“拉”的方法,观察者需要通过目标的引用(作为 update()方法的参数传入)来访问其属性。“拉”的方法需要让观察者更了解目标都拥有哪些属性,这增加了它们耦合度。而且主题也要对观察者门户大开,违背了封装性。解决的方法是在目标中提供一系列 getter 方法,如 getPassword()来让观察者获得用户的密码。

    虽然“拉”的方法可能被认为更加正确,但是我们觉得让主题把用户的信息“推”过来更加方便。既然通过在重写 update()方法时加上第 2 个参数是行不通的,那么就从别的方向上着手。好在 PHP 在方法调用上有这样的特性,只要给定的参数(实参)不少于定义时指定的必选参数(没有默认值的参数),PHP 就不会报错。传入一个方法的参数个数,可以通过 func_num_args() 函数获取;多余的参数可以使用 func_get_arg()函数读取。注意该函数是从 0 开始计数的,即 0 表示第 1 个实参。利用这个小技巧,update()方法可以通过 func_get_arg(1)接收一个用户信息的数组,有了这个数组,就能知道邮件该发给谁,新密码是什么了。为了节约篇幅,而且三个信息发送类非常相像,下面只给出其中一个的源代码,完整的源代码可以下载本文的附件得到。

    WebsiteSender.php 方法的部分源代码

    复制代码
     class WebsiteSender implements SplObserver {

        public function update(SplSubject $subject) {
            if (func_num_args() === 2) {
                $userInfo = func_get_arg(1);

                echo "这是1封站内小纸条。你好{$userInfo['username']},你的新密码是{$userInfo['password']}," .
                "请妥善保管<br/>";//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>
            }
        }

    }
    复制代码

     MobileSender.php 方法的部分源代码

    复制代码
    class MobileSender implements SplObserver {

        public function update(SplSubject $subject) {
            if (func_num_args() === 2) {
                $userInfo = func_get_arg(1);

                echo "向{$userInfo['mobile']}发送短消息成功。内容是:你好{$userInfo['username']}" .
                "你的新密码是{$userInfo['password']},请妥善保管<br/>";//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>
            }
        }

    }

    复制代码

    EmailSender.php 方法的部分源代码

    复制代码
    class EmailSender implements SplObserver {

        public function update(SplSubject $subject) {
            if (func_num_args() === 2) {
                $userInfo = func_get_arg(1);
                
                echo "向{$userInfo['email']}发送电子邮件成功。内容是:你好{$userInfo['username']}" .
                "你的新密码是{$userInfo['password']},请妥善保管<br/>";//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>
            }
        }

    }

    复制代码

    最后我们写一个测试脚本 test.php。建议使用 CLI 的方式 php – f test.php来执行该脚本,但由于设置了 Content-Type响应头部字段为 text/plain,在浏览器中应该也能看到一行一行显示的结果(因为没有用 <br />做换行符而是使用常量 PHP_EOL,所以不设置 Content-Type的话,就不能正确分行显示了)。

    test.php 方法的部分源代码 用于测试的脚本

    复制代码
    header("Content-type: text/html;charset=utf-8");

    function __autoload($class_name) {
        require_once "$class_name.php";
    }

    $email_sender = new EmailSender();
    $mobile_sender = new MobileSender();
    $web_sender = new WebsiteSender();

    $user = new User('user1@domain.com', '张三', '13610002000', '123456');

    // 创建用户时通过Email和手机短信通知用户
    $user->attach($email_sender);
    $user->attach($mobile_sender);
    $user->create($user);
    echo '<br/>';//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>

    // 用户忘记密码后重置密码,还需要通过站内小纸条通知用户
    $user->attach($web_sender);
    $user->resetPassword();
    echo '<br/>';//也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>

    // 用户变更了密码,但是不要给他的手机发短信
    $user->detach($mobile_sender);
    $user->changePassword('654321');
    echo '<br/>';
    //也可以用PHP内置函数换行PHP_EOL 原来是PHP_EOL我改成了<br/>

    复制代码

    运行结果

    复制代码
    User::create 
    向 user1@domain.com 发送电子邮件成功。内容是:你好张三你的新密码是 123456,请妥善保管
    向 13610002000 发送短消息成功。内容是:你好张三你的新密码是 123456,请妥善保管
    
     User::resetPassword 
    向 user1@domain.com 发送电子邮件成功。内容是:你好张三你的新密码是 363989,请妥善保管
    向 13610002000 发送短消息成功。内容是:你好张三你的新密码是 363989,请妥善保管
    这是 1 封站内小纸条。你好张三,你的新密码是 363989,请妥善保管
    
     User::changePassword 
    向 user1@domain.com 发送电子邮件成功。内容是:你好张三你的新密码是 654321,请妥善保管
    这是 1 封站内小纸条。你好张三,你的新密码是 654321,请妥善保管
    复制代码

    结束语 

    对于经验丰富的开发者,即使不使用 SPL 也可以轻松实现 Observer 设计模式,但是使用 SPL 带来了更高的效率,特别在结合了 SplObjectStorage之后,注册和删除观察者都由它的实例代理完成。虽然在使用“推”的方式更新 Observer 时,SplObserverupdate()方法只接受 1 个参数显得美中不足,或者说 SPL 内置的 Observer 设计模式只支持通过“拉模式”获取通知,但是通过本文的介绍的小技巧即可弥补。因此,SPL 在快速实现 Observer 设计模式上成为了首选。

     

    3.2观察者模式

    其实观察者模式这是一种较为容易去理解的一种模式吧,它是一种事件系统,意味着这一模式允许某个类观察另一个类的状态,当被观察的类状态发生改变的时候,观察类可以收到通知并且做出相应的动作。比如键盘,我一敲击,系统就收到通知并进行相应的回应。

    对于PHP来说,PHP内置提供了两个接口来供外部应用区实现这个模式。

    SplSubject 接口,它代表着被观察的对象,

    其结构:

     
    <?php
    interface SplSubject{
        public function attach(SplObserver $observer);
        public function detach(SplObserver $observer);  
        public function notify();
    }
     

    SplObserver 接口,它代表着充当观察者的对象,

    其结构:

     
    <?php
    interface SplObserver{ 
        public function update(SplSubject $subject);
    }
     

     这一个模式是这样实现的。SplSubject维护了一个特定的状态,当这个状态发生变化时,它就用notify()来通知之前用attach注册到SplSubject的所有SplObserver,并且调用其相应的update方法。
    简单的例子:

    复制代码
    <?php
    class subject implements SplSubject
    {
        private $observers , $value;
        public function __construct()
        {
            $this->observers = array();                
        }             
        public function attach(SplObserver $observer)
        {
            $this->observers[] = $observer;
        }             
        public function detach(SplObserver $observer)
        {                    
            if($idx = array_search($observer, $this->observers, true))
            {                           
                unset($this->observers[$idx]);                         
            }             
        }             
        public function notify()
        {                    
            foreach($this->observers as $observer)
            {                           
                $observer->update($this);                    
            }             
        }             
        public function setValue($value)
        {                    
            $this->value = $value;                    
            $this->notify();             
        }             
        public function getValue()
        {                    
            return $this->value;                  
        }      
    }       
    class observer implements SplObserver
    {             
        public function update(SplSubject $subject)
        {                    
            echo 'The new state of subject'.$subject->getValue();              
        }          
    }       
    $subject = new subject();      
    $observer = new observer();      
    $subject->attach($observer);      
    $subject->setValue(5);
    复制代码

     参考原文:http://www.php1.cn/Content/PHP_GuanChaZheMoShi_PHPSplSubjectSplObserver_JieKouShiXian.html

    4.注册模式

    注册模式,解决全局共享和交换对象。已经创建好的对象,挂在到某个全局可以使用的数组上,在需要使用的时候,直接从该数组上获取即可。将对象注册到全局的树上。任何地方直接去访问。

    class Register
    {
        protected static  $objects;
            function set($alias,$object)//将对象注册到全局的树上
            {
                self::$objects[$alias]=$object;//将对象放到树上
            }
            static function get($name){
            return self::$objects[$name];//获取某个注册到树上的对象
        }
        function _unset($alias)
      {
            unset(self::$objects[$alias]);//移除某个注册到树上的对象。
        }
    }
    
    AutoRegister::set('single',$single);
    $single = AutoRegister::get('single');
    var_dump($single);

    单例模式可以用于数据库的链接,再用工厂模式,在用注册模式

    5.适配器模式

    适配器模式:即将截然不同的函数接口封装成统一的接口API

    例如 MYSQL的数据库扩展操作 mysql,mysqli,pdo三种,可以用适配器模式统一成一致.

    类似的场景还有cache操作,例如 redis,memcached,mongodb,apc等不同的缓存函数,统一成一致

    /**
     * 适配器模式
     * 可以将不同的函数接口封装成统一的API
     * 应用举例1:数据库操作 mysql/mysqli/pdo
     * 应用举例2:缓存适配器 memcache/redis/file/apc
     */
    

    //定义接口, interface Itface{ function connect($host, $user, $passwd, $dbname); function query($sql); function close(); } //创建类并继承接口 class Pdos implements Itface{ protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = new PDO("mysql:host=$host;dbname=$dbname", $user, $passwd); $this->conn = $conn; } function query($sql) { return $this->conn->query($sql); } function close() { unset($this->conn); } } class MySQL implements Itface{ protected $conn; function connect($host, $user, $passwd, $dbname) { $conn = mysql_connect($host, $user, $passwd); mysql_select_db($dbname, $conn); $this->conn = $conn; } function query($sql) { $res = mysql_query($sql, $this->conn); return $res; } function close() { mysql_close($this->conn); } } $db = new Pdos(); //在这里轻松切换PDO和MySQL,下面的代码不变仍能达到一致的结果 $db -> connect('127.0.0.1','root','123456','tp2'); $arr=$db->query('select * from '); $db->close(); $res=$arr->fetch_assoc(); var_dump($res);

    6.策略模式

     

    策略模式,又称为政策模式,属于行为型的设计模式。

    Gof类图及解释

    GoF定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化 。

    GoF类图

    代码实现
    interface Strategy{
        function AlgorithmInterface();
    }
    
    class ConcreteStrategyA implements Strategy{
        function AlgorithmInterface(){
            echo "算法A";
        }
    }
    
    class ConcreteStrategyB implements Strategy{
        function AlgorithmInterface(){
            echo "算法B";
        }
    }
    
    class ConcreteStrategyC implements Strategy{
        function AlgorithmInterface(){
            echo "算法C";
        }
    }

    定义算法抽象及实现。

    class Context{
        private $strategy;
        function __construct(Strategy $s){
            $this->strategy = $s;
        }
        function ContextInterface(){
            
            $this->strategy->AlgorithmInterface();
        }
    }

    定义执行环境上下文。

    $strategyA = new ConcreteStrategyA();
    $context = new Context($strategyA);
    $context->ContextInterface();
    
    $strategyB = new ConcreteStrategyB();
    $context = new Context($strategyB);
    $context->ContextInterface();
    
    $strategyC = new ConcreteStrategyC();
    $context = new Context($strategyC);
    $context->ContextInterface();

    最后,在客户端按需调用合适的算法。

    • 是不是非常简单的一个设计模式。大家有没有发现这个模式和我们最早讲过的简单工厂非常类似
    • 那么他们的区别呢?
    • 工厂相关的模式属于创建型模式,顾名思义,这种模式是用来创建对象的,返回的是new出来的对象。要调用对象的什么方法是由客户端来决定的
    • 而策略模式属性行为型模式,通过执行上下文,将要调用的函数方法封装了起来,客户端只需要调用执行上下文的方法就可以了
    • 在这里,我们会发现,需要客户端来实例化具体的算法类,貌似还不如简单工厂好用,既然这样的话,大家何不尝试一下结合工厂和策略模式一起来实现一个模式呢?
    • 作为思考题将这个实现留给大家,提示:将Context类的__construct变成一个简单工厂方法

    既然和简单工厂如此的相像,那么我们也按照简单工厂的方式来说:我们是一个手机厂商(Client),想找某工厂(ConcreteStrategy)来做一批手机,通过渠道商(Context)向这个工厂下单制造手机,渠道商直接去联系代工厂(Strategy),并且直接将生产完成的手机发货给我(ContextInterface())。同样的,我不用关心他们的具体实现,我只要监督那个和我们联系的渠道商就可以啦,是不是很省心!

    完整代码:

    实例1

    依然还是短信功能,具体的需求可以参看简单工厂模式中的讲解,但是这回我们使用策略模式来实现!

    短信发送类图

    完整源码:

    <?php
    
    interface Message
    {
        public function send();
    }
    
    class BaiduYunMessage implements Message
    {
        function send()
        {
            echo '百度云发送信息!';
        }
    }
    
    class AliYunMessage implements Message
    {
        public function send()
        {
            echo '阿里云发送信息!';
        }
    }
    
    class JiguangMessage implements Message
    {
        public function send()
        {
            echo '极光发送信息!';
        }
    }
    
    class MessageContext
    {
        private $message;
        public function __construct(Message $msg)
        {
            $this->message = $msg;
        }
        public function SendMessage()
        {
            $this->message->send();
        }
    }
    
    $bdMsg = new BaiduYunMessage();
    $msgCtx = new MessageContext($bdMsg);
    $msgCtx->SendMessage();
    
    $alMsg = new AliYunMessage();
    $msgCtx = new MessageContext($alMsg);
    $msgCtx->SendMessage();
    
    $jgMsg = new JiguangMessage();
    $msgCtx = new MessageContext($jgMsg);
    $msgCtx->SendMessage();
    说明
    • 注意对比下类图,基本和简单工厂模式没什么区别
    • 策略模式定义的是算法,从概念上看,这些算法完成的都是相同的工作,只是实现不同,但东西是死的,人是活的,具体想怎么用,还不是看大家的兴趣咯
    • 策略模式可以优化单元测试,因为每个算法都有自己的类,所以可以通过自己的接口单独测试

    实例2

    例如:一个电商首页,可以根据登陆用户的性别不同显示不同的内容,比如广告和商品类别。

    传统方法:在程序内部使用多个if,else进行判断,这样很难维护,比如我想要再加入一个人妖用户!那么我就要在程序中再加入else if判断,这样写出来的程序看起来很乱,而且不易维护。

    策略模式:实现的思想是这样的,创建一个接口,对于每个类别的用户创建一个类,分别去实现接口中的方法,在首页进行判断实例化哪个类。

    接口文件UserStrategy.php

    <?php
    //定义接口,我要展示广告和类别
    interface UserStrategy
    {
      //显示广告的方法
      public function showAd();
      //显示类别的方法
      public function showCategory();
    }
    ?>

    男性用户的具体显示MaleUserStrategy.php

    <?php
    //强制实现接口中的方法
    class MaleUserStrategy implements UserStrategy
    {
      //男性用户登录后会显示iphone6s的广告
      public function showAd()
      {
        echo 'Iphone6s';
      }
      //会显示电子产品类别
      public function showCategory()
      {
        echo '电子产品啊';
      }
    }
    ?>

    女性用户的具体显示FemaleUserStrategy.php

    <?php
    class FemaleUserStrategy implements UserStrategy
    {
      public function showAd()
      {
        echo '2016女装';
      }
      public function showCategory()
      {
        echo '女装';
      }
    }
    ?>

    重点来了,创建首页文件Strategy.php

    <?php
    //如果我想使用上面那些类的话就需要一个文件一个文件的require,但我们可以使用一种方法来自动加载所需要的文件
    //首先定义一个基本的文件路径,因为这些文件我都是放在一个文件夹下的
    define('BASEDIR', __DIR__);
    //载入一个自动require文件的文件,这里我写成Config.php
    require 'Config.php';
    //PHP提供的spl库,这我们就把需要的文件都加载进来了
    //那么spl_autoload_register()是什么意思呢?其实就是,当程序运行碰到了没有定义的类的时候就会自动执行它里面的函数,所以就把文件加载近来了呀!
    spl_autoload_register('Config::autoload');
    class Strategy
    {
      //定义一个变量来存储策略
      protected $_strategy;
      //定义展示广告和类别的方法
      public function index()
      {
        echo 'Ad:';
        echo $this->_strategy->showAd();
        echo '<br/>';
        echo 'Category:';
        echo $this->_strategy->showCategory();
      }
      //创建不同的策略
      public function setStrategy($strategy)
      {
        $this->_strategy = $strategy;
      }
    }
    //实例化对象
    $strategy = new Strategy();
    //如果$_GET['female']存在的话就显示女性用户的广告和类别--如果需要添加其它策略的时候只需要在这里加入判断条件就可以了,当然了还需要创建相应策略的类并且一定要去继承接口哦
    if(isset($_GET['female']))
    {
      $people = new FemaleUserStrategy();
    }
    else
    {
      $people = new MaleUserStrategy();
    }
    //把对象传入到策略类里
    $strategy = setStrategy($people);
    //显示广告和类别--因为使用了策略模式,当我们需要新增加一个策略的时候这里就不需要修改了。
    $strategy->index();
    ?>

    7.数据对象映射模式

    还是代码说话:这里还是遵循策略模式的psr-0代码规范

    入口文件DataUser.php

    <?php
    define('BASEDIR', __DIR__);
    //自动加载在本文件中没有被定义的类
    require 'Config.php';
    spl_autoload_register('Config::autolad');
    //获取数据
    $user = new Data(1);
    var_dump($user->id, $user->name, $user->money);
    //如果想要修改数据
    $user->id = 1;
    $user->name = 'zhangjianping';
    $user->money = 10000;
    ?>

    获取数据的文件Data.php

    <?php
    class Data
    {
    //数据项
    public $id;
    public $name;
    public $money;
    //数据库连接对象
    protected $con;
    //查询数据的构造函数
    public function __construct($id)
    {
      //连接数据库
      $this->con = DB::getInstance()->connect();
      //查询数据
      $res = $this->con->query('select * from account where id = '.$id.' limit 1');
      $data = $res->fetch(PDO::FETCH_ASSOC);
      //把取出来的数据项存储起来
      $this->id = $data['id'];
      $this->name = $data['name'];
      $this->money = $data['money'];
    }
    //修改数据的析构函数
    public function __destruct()
    {
      $this->con->query("update account set name = '{$this->name}', 'money = {$this->money}' where id = {$this->id}");
    }
    }
    ?>

    下面我们就使用工厂模式,注册树模式,数据对象映射模式来完善一下这个例子

    • 数据库连接文件Db.php
    • 自动加载类文件Config.php
    • 获取数据的文件Data.php

    我们将原来的入口文件改一下:

    DataUser.php

    <?php
    define('BASEDIR', __DIR__);
    require 'Config.php';
    spl_autoload_register(Config::autoload);
    class DataUser
    {
      public function index()
      {
        //使用工厂模式来生成对象
        $user = Factory::getUser(1);
        var_dump($user->id);
        $this->name();
        $this->money();
      }
      public function name()
      {
        $user = Factory::getUser(1);
        var_dump($user->name);
      }
      public function money()
      {
        $user = Factory::getUser(1);
        var_dump($user->money);
      }
    }
    ?>

    工厂类Factory.php

    <?php
    class Factory
    {
      static function getUser($id)
      {
        //这里使用注册器模式,不然的话,在上面的文件中,使用工厂模式生成对象得时候就会多次创建对象,很占用资源
        //根据id不同插入到注册树对象中
        $key = 'user_'.$id;
        //从注册器中取出对象
        $user = Register::get($key);
        //如果注册器中没有就创建一个对象并注册上去
        if(!isset($user))
        {
          $user = new Data($id);
          $user = Register::set($key, $user);
        }
        return $user;
      }
    }
    ?>

    注册器类Register.php

    <?php
    class Register
    {
      //存储对象得变量
      protected static $object;
      //注册入注册器
      public static function set($key, $value)
      {
        self::$object[$key] = $value;
      }
      //从注册器中取出
      public static function get($key)
      {
        return self::$object[$key];
      }
      //从注册器中删除
      public static function _unset($key)
      {
        unset(self::$object[$key]);
      }
    }
    ?>

    如果这时候我们将Data.php修改为Data1.php,那么在不使用工厂模式的时候就要一个一个的去修改类名,现在只需要在工厂模式中修改一下就好了,我们也可以打印出每一个对象,这时候我们会发现这3个对象都是一样的,这是因为我们使用了注册器模式。

     视频教程:(http://www.imooc.com/learn/236)

  • 相关阅读:
    SpringBoot之旅第三篇-日志
    SpringBoot之旅第二篇-配置
    SpringBoot之旅第一篇-初探
    394. 字符串解码
    1190. 反转每对括号间的子串
    921. 使括号有效的最少添加
    Leetcode 1171. 从链表中删去总和值为零的连续节点
    设计模式之过滤器模式——Java语言描述
    MySQL查询执行的基础
    设计模式之桥接模式——Java语言描述
  • 原文地址:https://www.cnblogs.com/xiangshihua/p/5488911.html
Copyright © 2011-2022 走看看