今天说一下这个PHP八大设计模式

单例模式

$_instance必须声明为静态的私有变量

构造函数和析构函数必须声明为私有,防止外部程序new 类从而失去单例模式的意义

getInstance()方法必须设置为公有的,必须调用此方法 以返回实例的一个引用

::操作符只能访问静态变量和静态函数

new对象都会消耗内存

使用场景:最常用的地方是数据库连接。

使用单例模式生成一个对象后, 该对象可以被其它众多对象所使用。

私有的__clone()方法防止克隆对象

单例模式,使某个类的对象仅允许创建一个。构造函数private修饰,

申明一个static getInstance方法,在该方法里创建该对象的实例。如果该实例已经存在,则不创建。比如只需要创建一个数据库连接。

示例

<?php


namespace App\Http\Controllers\Admin;


use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\ValidationData;

class JwtAuth
{
    private $token;
    private $user_id;
    private $secrect="#awadakvcabzijvjvsuvr";
    private $aud="www.test.com";
    private $iss="www.test.com";
    private $decodeToken;
    /**
     * JTW句柄
     * @var
     */
    private static $instance;

    public static function getInstance(){
        if (is_null(self::$instance)){
            self::$instance =new self();
        }
        return self::$instance;
    }

    private function __construct()
    {
    }

    private function __clone(){

    }

    public function getToken(){
        return (string)$this->token;
    }

    public function setToken($token){
        $this->token = $token;

        return $this;
    }

    public function setUid($user_id){
        $this->user_id =$user_id;
        return $this;
    }

    public function encode(){
        $time= time();
       $this->token = (new Builder())
           ->setHeader('alg','HS256')
           ->setIssuer($this->iss)
           ->setAudience($this->aud)
           ->setIssuedAt($time)
           ->setExpiration($time+3600)
           ->set('user_id',$this->user_id)
           ->sign(new Sha256(), $this->secrect)
           ->getToken();
       return $this;
    }

    /**
     * @return \Lcobucci\JWT\Token
     */
    public function decode(){
        if (!$this->decodeToken){
            $this->decodeToken = (new Parser())->parse((string)$this->token);
            $this->user_id = $this->decodeToken->getClaim('user_id');
        }

        return $this->decodeToken;
    }

    /**
     * @return bool
     */
    public function validate(){
        $data = new ValidationData();
        $data->setIssuer($this->iss);
        $data->setAudience($this->aud);

        return $this->decode()->validate($data);
    }

    /**
     * @return bool
     */
    public function verify(){
        $result = $this->decode()->verify(new Sha256() , $this->secrect);
        return $result;
    }

    public function getUid(){
      return (new Parser())->parse((string)$this->token)->getClaim('user_id');
    }



}

调用

$jwtAuth = JwtAuth::getInstance();
$token = $jwtAuth->setUid($id)->encode()->getToken();

工厂模式

工厂模式,工厂方法或者类生成对象,而不是在代码中直接new。

使用工厂模式,可以避免当改变某个类的名字或者方法之后,在调用这个类的所有的代码中都修改它的名字或者参数。

示例

// 最基本的工厂模式  

class Myname{  
    public function OutPutMyName(){  
        return 'hello world~';  
    }  
}

class NameFactory{  
    public static function Namefunc(){  
        return new Myname();  
    }  
}

$obj=NameFactory::Namefunc();  
echo $obj->OutPutMyName();  
?>  

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽象类,也就是说,工厂方法模式提供的所有产品都是衍生自同一个接口或抽象类,而抽象工厂模式所提供的产品则是衍生自不同的接口或抽象类。

<?php  
//定义一个抽象类  
abstract class operation  
{  
    protected $_numA = 0;  
    protected $_numB = 0;  
    protected $_result = 0;  
    public function __construct($a, $b)  
    {  
        $this->_numA = $a;  
        $this->_numB = $b;  
    }  
    //抽象方法所有子类必须实现该方法  
    protected abstract function getResult();  
}  
//加法运算  
class operationAdd extends operation  
{  
    public function getResult()  
    {  
        $this->_result = $this->_numA + $this->_numB;  
        return $this->_result;  
    }  
}  
//减法运算  
class operationSub extends operation  
{  
    public function getResult()  
    {  
        $this->_result = $this->_numA - $this->_numB;  
        return $this->_result;  
    }  
}  
//乘法运算  
class operationMul extends operation  
{  
    public function getResult()  
    {  
        $this->_result = $this->_numA * $this->_numB;  
        return $this->_result;  
    }  
}  
//除法运算  
class operationDiv extends operation  
{  
    public function getResult()  
    {  
        $this->_result = $this->_numA / $this->_numB;  
        return $this->_result;  
    }  
}  
//定义工厂类  
class operationFactory  
{  
    //创建保存示例的静态成员变量  
    private static $obj;  
    //创建实例的静态方法  
    public static function CreateOperation($type, $a, $b)  
    {  
        switch ($type) {  
        case '+':  
            self::$obj = new operationAdd($a, $b);  
            break;  
        case '-':  
            self::$obj = new operationSub($a, $b);  
            break;  
        case '*':  
            self::$obj = new operationMul($a, $b);  
            break;  
        case '/':  
            self::$obj = new operationDiv($a, $b);  
            break;  
        }  
        //最后返回这个实例  
        return self::$obj;  
    }  
}  
//最后我们使用工厂模式  
$obj = operationFactory::CreateOperation('+', 100, 20);  
echo $obj->getResult(); 

如果还是理解不了这两者的区别,可以看看这个帖子: https://www.zhihu.com/question/20367734

注册模式

单例模式保证了一个类中只有一个实例被全局访问,当你有一组全局对象被全局访问时可能就需要用到注册者模式 (registry),它提供了在程序中有条理的存放并管理对象 (object)一种解决方案。一个“注册模式”应该提供get() 和 set()方法来存储和取得对象(用一些属性key)而且也应该提供一个isValid()方法来确定一个给定的属性是否已经设置。 注册模式通过单一的全局的对象来获取对其它对象的引用

示例

class  Registry  {

    protected   static  $store  =  array();
    private     static  $instance;

    public static function instance() {
        if(!isset(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function  isValid($key)  {
        return  array_key_exists($key,Registry::$store);
    }

    public function  get($key)  {
        if  (array_key_exists($key,Registry::$store))
            return  Registry::$store[$key];
    }

    public  function  set($key,  $obj)  {
        Registry::$store[$key]  =  $obj;
    }
}

//数据库链接类
class ConnectDB {

    private $host;
    private $username;
    private $password;

    private $conn;

    public function __construct($host, $username, $password){
        $this->host = $host;
        $this->username = $username;
        $this->password = $password;
    }

    public function getConnect() {
        return mysql_connect($this->host,$this->username,$this->password);
    }

}

$reg = Registry::instance();
$reg->set('db1', new ConnectDB('localhost', 'root', '123456'));
$reg->set('db2', new ConnectDB('127.0.0.2', 'test', 'test'));

print_r($reg->get('db1'));
print_r($reg->get('db2'));

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

示例

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]);
     }
}

适配器模式(interface)

将各种截然不同的函数接口封装成统一的API。

PHP中的数据库操作有MySQL,MySQLi,PDO三种,可以用适配器模式统一成一致,使不同的数据库操作,统一成一样的API。类似的场景还有cache适配器,可以将memcache,redis,file,apc等不同的缓存函数,统一成一致。

首先定义一个接口(有几个方法,以及相应的参数)。然后,有几种不同的情况,就写几个类实现该接口。将完成相似功能的函数,统一成一致的方法。

示例

// 接口 IDatabase
 <?php
 namespace IMooc;
 interface IDatabase
 {
     function connect($host, $user, $passwd, $dbname);
     function query($sql);
     function close();
 }

MySQL
<?php
namespace IMooc\Database;
use IMooc\IDatabase;
class MySQL implements IDatabase
{
    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);
    }
}

PDO
<?php
namespace IMooc\Database;
use IMooc\IDatabase;
class PDO implements IDatabase
{
    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);
    }
}

示例二

/**
 * 目标角色
 */
interface Target {
 
    /**
     * 源类也有的方法1
     */
    public function sampleMethod1();
 
    /**
     * 源类没有的方法2
     */
    public function sampleMethod2();
}
 
/**
 * 源角色
 */
class Adaptee {
 
    /**
     * 源类含有的方法
     */
    public function sampleMethod1() {
        echo 'Adaptee sampleMethod1 <br />';
    }
}
 
/**
 * 类适配器角色
 */
class Adapter extends Adaptee implements Target {
 
    /**
     * 源类中没有sampleMethod2方法,在此补充
     */
    public function sampleMethod2() {
        echo 'Adapter sampleMethod2 <br />';
    }
 
}
 
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
        $adapter = new Adapter();
        $adapter->sampleMethod1();
        $adapter->sampleMethod2();
 
    }
 
}

策略模式

策略模式,将一组特定的行为和算法封装成类,以适应某些特定的上下文环境。

eg:假如有一个电商网站系统,针对男性女性用户要各自跳转到不同的商品类目,并且所有的广告位展示不同的广告。在传统的代码中,都是在系统中加入各种if else的判断,硬编码的方式。如果有一天增加了一种用户,就需要改写代码。使用策略模式,如果新增加一种用户类型,只需要增加一种策略就可以。其他所有的地方只需要使用不同的策略就可以。

首先声明策略的接口文件,约定了策略的包含的行为。然后,定义各个具体的策略实现类。

示例

UserStrategy.php
<?php
/*
 * 声明策略文件的接口,约定策略包含的行为。
 */
interface UserStrategy
{
    function showAd();
    function showCategory();
}

FemaleUser.php
<?php
require_once 'Loader.php';
class FemaleUser implements UserStrategy
{
    function showAd(){
        echo "2016冬季女装";
    }
    function showCategory(){
        echo "女装";
    }
}

MaleUser.php
<?php
require_once 'Loader.php';
class MaleUser implements UserStrategy
{
    function showAd(){
        echo "IPhone6s";
    }
    function showCategory(){
        echo "电子产品";
    }
}

Page.php//执行文件
<?php
require_once 'Loader.php';
class Page
{
    protected $strategy;
    function index(){
        echo "AD";
        $this->strategy->showAd();
        echo "<br>";
        echo "Category";
        $this->strategy->showCategory();
        echo "<br>";
    }
    function setStrategy(UserStrategy $strategy){
        $this->strategy=$strategy;
    }
}

$page = new Page();
if(isset($_GET['male'])){
    $strategy = new MaleUser();
}else {
    $strategy = new FemaleUser();
}
$page->setStrategy($strategy);
$page->index();

php策略模式和适配器模式的区别

PHP中策略模式和适配器模式的区别是:策略模式是通过定义算法,并将算法封装起来,使算法独立于客户而独立变化;适配器模式是使用已经存在的类,将其适配成自己需要的形式。

观察者模式(Observer)

(Observer),首先要有一个被观察的角色,但ta是【唯一的】。虽然”表演者”只有一个但是”观众”有很多,既一群”人”围观一个”人”。既然有无数个观察者,那么我们需要知道都有哪一些”人”。所以我们需要一个“容器”来记录这些”人”,一个类似于数组一样来储存所有观察者的容器。

一个”演员”(被观察者),一群”观众”(观察者),一台”摄影机”(记录容器)

1.抽象主题(Subject)角色:主题角色将所有对观察者对象的引用保存在一个集合中,每个主题可以有任意多个观察者。 抽象主题提供了增加和删除观察者对象的接口。

2.抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在观察的主题发生改变时更新自己。

3.具体主题(ConcreteSubject)角色:存储相关状态到具体观察者对象,当具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

4.具体观察者(ConcretedObserver)角色:存储一个具体主题对象,存储相关状态,实现抽象观察者角色所要求的更新接口,以使得其自身状态和主题的状态保持一致

主题接口

interface Subject{
    public function register(Observer $observer);
    public function notify();
}

观察者接口

interface Observer{
    public function watch();
}

Subject就是被观察者,Observer就是观众,也就是观察者

被观察者

class Action implements Subject{
     public $_observers=array();
     public function register(Observer $observer){
         $this->_observers[]=$observer;
     }
 
     public function notify(){
         foreach ($this->_observers as $observer) {
             $observer->watch();
         }
 
     }
 }

Action实现了被观察者接口,他现在就是被观察者,再定义一个$_observers数组,他就是记录观众的容器了。

首先实现register方法,用它传入一个观察者,然后塞到数组里,再实现notify()方法,它会遍历容器数组,执行每个观察者的watch()方法。

观察者

class Cat implements Observer{
     public function watch(){
         echo "Cat watches TV<hr/>";
     }
 }
 class Dog implements Observer{
     public function watch(){
         echo "Dog watches TV<hr/>";
     }
 }
 class People implements Observer{
     public function watch(){
         echo "People watches TV<hr/>";
     }
 }

这里定义了三个观察者,全都实现了Observer接口,前面的Subject会循环调用每个观察者的watch()方法,所以我们需要实现每个观察者的watch()方法。

调用

$action=new Action();
$action->register(new Cat());
$action->register(new People());
$action->register(new Dog());
$action->notify();

首先new被观察者对象,执行它的register()方法,把每个观察者都放入容器数组,最后执行notify()方法,通知所有观察者执行自己的方法。

原型模式

为了避免创建对象的成本,使用标准方法 new foo() 创建一个原型并克隆它。

场景

大数据量 ( 例如:通过 ORM 模型一次性往数据库插入 1,000,000 条数据 ) 。

示例

<?php
abstract class BookPrototype
{
    /*
    * @var string
    */
    protected $title;
    
    /*
    * @var string
    */
    protected $category;
    
    abstract public function __clone();
    
    public function getTitle(): string
    {
        return $this->title;
    }// getTitle() end
    
    public function setTitle($title)
    {
        $this->title = $title;
    }// setTitle() end
}// BookPrototype{} end

class BarBookPrototype extends BookPrototype
{
    /*
    * @var string
    */
    protected $category = 'Bar';
    
    public function __clone()
    {
    }
}// BarBookPrototype{} end

class FooBookPrototype extends BookPrototype
{
    /*
    * @var string
    */
    protected $category = 'Foo';
    
    public function __clone()
    {
    }
}// FooBookPrototype{} end

$fooPrototype = new FooBookPrototype();
$barPrototype = new BarBookPrototype();

for ($i = 0; $i < 10; $i++) {
    $book = clone $fooPrototype;
    $book->setTitle('Foo Book No ' . $i);
    echo $book->getTitle() . PHP_EOL;
}

for ($i = 0; $i < 5; $i++) {
    $book = clone $barPrototype;
    $book->setTitle('Bar Book No ' . $i);
    echo $book->getTitle() . PHP_EOL;
}

结果

<?php
Foo Book No 0
Foo Book No 1
Foo Book No 2
Foo Book No 3
Foo Book No 4
Foo Book No 5
Foo Book No 6
Foo Book No 7
Foo Book No 8
Foo Book No 9
Bar Book No 0
Bar Book No 1
Bar Book No 2
Bar Book No 3
Bar Book No 4

PHP 对象复制 : 对象复制可以通过 clone 关键字来完成

装饰器模式

动态地给一个对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活

例子

短信模板接口

<?php
interface MessageTemplate
{
    public function message();
}

假设有很多模板实现了上面的短信模板接口

// 下面这个是其中一个优惠券发送的模板实现
class CouponMessageTemplate implements MessageTemplate
{
    public function message()
    {
        return '优惠券信息:我们是全国第一的牛X产品哦,送您十张优惠券!';
    }
}

我们来准备好装饰上面那个过时的短信模板

abstract class DecoratorMessageTemplate implements MessageTemplate
{
    public $template;
    public function __construct($template)
    {
        $this->template = $template;
    }
}

过滤新广告法中不允许出现的词汇

class AdFilterDecoratorMessage extends DecoratorMessageTemplate
{
    public function message()
    {
        return str_replace('全国第一', '全国第二', $this->template->message());
    }
}

使用我们的大数据部门同事自动生成的新词库来过滤敏感词汇,这块过滤不是强制要过滤的内容,可选择使用

class SensitiveFilterDecoratorMessage extends DecoratorMessageTemplate
{
    public $bigDataFilterWords = ['牛X'];
    public $bigDataReplaceWords = ['好用'];
    public function message()
    {
        return str_replace($this->bigDataFilterWords, $this->bigDataReplaceWords, $this->template->message());
    }
}

客户端,发送接口,需要使用模板来进行短信发送

class Message
{
    public $msgType = 'old';
    public function send(MessageTemplate $mt)
    {
        // 发送出去咯
        if ($this->msgType == 'old') {
            echo '面向内网用户发送' . $mt->message() . PHP_EOL;
        } else if ($this->msgType == 'new') {
            echo '面向全网用户发送' . $mt->message() . PHP_EOL;
        }

    }
}

$template = new CouponMessageTemplate();
$message = new Message();

// 老系统,用不着过滤,只有内部用户才看得到
$message->send($template);

// 新系统,面向全网发布的,需要过滤一下内容哦
$message->msgType = 'new';
$template = new AdFilterDecoratorMessage($template);
$template = new SensitiveFilterDecoratorMessage($template);

// 过滤完了,发送吧
$message->send($template);

正文完