设计模式在PHP业务场景中的应用

1.设计模式概述

设计模式是一把双刃剑,在工做场景中,只有不多的设计模式会被在业务场景中使用到,最经常使用的就是单例模式。相反,工做中使用的框架则在底层使用到了大量的设计模式,这样作提升了框架的性能、更好的解耦各个模块之间的功能、提供更好的拓展性、最大程度简化了开发者使用成本。实际上,在业务场景中使用合适的设计模式,也会达到事半功倍的效果。不过要想使用好设计模式,必需要深入理解它设计的初衷和适用的场景分别是什么,这并非一件容易的事情!本文结合四种常见的设计模式,聊一聊在php应用开发场景中,怎样更好的使用设计模式来简化业务逻辑,使得需求可拓展,代码更容易维护。本文使用到的案例放在:设计模式 ,先来学习一下基本知识:设计模式的七大原则、分类和UML类图的使用。php

1.1 设计模式的七大原则

  • 开闭原则( Open Close Principle );在对程序进行更新迭代的过程当中,应当合理的避免修改类或方法的内部代码,而是优先选择经过继承、扩展等方式来实现。简而言之,就是:对扩展开放,对修改关闭。
  • 里氏替换原则( Liskov Substitution Principle );在实现子类的定义时,应该让它彻底拥有替代父类进行工做的能力。简而言之,就是:子类对外要具与父类一致的方法或接口。
  • 依赖倒置原则( Dependence Inversion Principle );在对象或类的依赖关系定义上,父类或者其余上层实现不该该依赖于子类或者其余下层实现,经过这样,来避免依赖关系的耦合。
  • 单一职责原则( Single Responsibility Principle );在程序结构和依赖关系的定义上,要将类的功能职责充分理清,尽力减小类之间的耦合。避免对某个类进行修改时,牵一发动全身的连锁反应。
  • 接口隔离原则( Interface Segregation Principle );在对外接口的定义上,要避免庞大而臃肿的接口,而是进行责任细化的区分,避免冗余的代码实现。这对于提升内聚,提高系统灵活度是很是有效果的。
  • 最少知识原则( Least Knowledge Principle );在分配类的职责和创建依赖关系时,应该只关注于自身的功能实现和周围与之接触类的交互方式。避免类去考虑整个系统结构和处理流程,让类的职责清晰化,让系统的耦合度下降。
  • 合成复用原则( Composite Reuse Principle );在扩展功能的时候,要优先考虑水平形式的新增类或方法,而不是经过继承去实现。也就是经过功能的组合实现类,而不是经过基础去实现新的功能。这样能够提升类的可扩展性,减小系统的层次。

这七大原则为咱们设计程序提供了指导,能够说是优秀程序设计的方法论。 不过理论每每又是简短而抽象的,你们想要理解并熟练用它们去指导程序设计,还需从大量的实践中去领悟。下边咱们介绍使用设计模式的时候也会根据这七大原则设计。html

1.2 设计模式的分类

设计模式自己是很是丰富的,通常将面向对象的设计模式分为三类:建立型、结构型和结构型。mysql

1.2.1 建立型git

建立对象时,再也不由咱们直接实例化对象;而是根据特定场景,由程序来肯定建立对象的方式,从而保证更大的性能、更好的架构优点。 建立型模式主要有:程序员

  • 单例模式(经常使用)
  • 工厂模式:简单工厂模式、工厂方法模式、抽象工厂模式(经常使用)(注:简- 单工厂模式不属于23种设计模式)
  • 生成器模式
  • 原型模式

1.2.2 结构型github

用于帮助将多个对象组织成更大的结构。面试

结构型模式主要有:sql

  • 适配器模式(经常使用)
  • 装饰器模式
  • 代理模式
  • 门面模式(外观模式)
  • 桥接模式
  • 组合模式
  • 亨元模式

1.2.3 行为型数据库

用于帮助系统间各对象的通讯,以及如何控制复杂系统中流程。设计模式

行为型模式主要有:

  • 策略模式(经常使用)
  • 观察者模式(经常使用)
  • 模板模式
  • 迭代器模式
  • 职责链模式
  • 命令模式
  • 备忘录模式
  • 状态模式
  • 访问者模式
  • 中介者模式
  • 解释器模式

1.3 UML类图的使用

不少东西使用文字表述是苍白无力的,尤为是设计模式这种抽象的理论。咱们使用UML类图来增长咱们的表达力。类图(Class diagram)主要用于描述系统的结构化设计。类图也是最经常使用的UML图,用类图能够显示出类、接口以及它们之间的静态结构和关系。

在UML类图中,常见的有如下几种关系:

  • 继承/泛化(Generalization):用于描述父类与子类之间的关系。父类又称做基类,子类又称做派生类。经过extends实现,如:class Bus extends Car
  • 实现(Realization),主要用来规定接口和实现类的关系。经过implements实现,如:class Car implements Vehicle
  • 关联(Association)
  • 聚合(Aggregation)
  • 组合(Composition)
  • 依赖(Dependency)

这六种类关系中,组合、聚合和关联的代码结构同样,能够从关系的强弱来理解,各种关系从强到弱依次是:继承→实现→组合→聚合→关联→依赖。(目前本身的理解)除了继承和实现,其余类关系较弱,通常是经过在一个类中调用其余类来实现。

2.四种设计模式在业务场景中的应用

咱们本节介绍的四种设计模式分别是:工厂模式、装饰器模式、发布/订阅模式、迭代器模式。选择这四种设计模式是由于在PHP业务场景中会常常用到他们,笔者还整理了一些相关的一些案例。

2.1 工厂模式

“工厂模式”简单来说就是将建立对象的任务交给工厂,根据抽象层次的不一样,又分为:简单工厂、工厂方法和抽象工厂。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类建立型模式。在简单工厂模式中,能够根据参数的不一样返回不一样类的实例。简单工厂模式专门定义一个类来负责建立其余类的实例,被建立的实例一般都具备共同的父类。它的抽象层次低,工厂类通常也不会包含复杂的对象生成逻辑,只能适用于生成结构比较简单,扩展性要求较低的对象。

工厂方法模式(Factory Method Pattern): 又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类建立型模式。在工厂方法模式中,工厂父类负责定义建立产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样作的目的是将产品类的实例化操做延迟到工厂子类中完成,即经过工厂子类来肯定究竟应该实例化哪个具体产品类。

抽象工厂模式(Abstract Factory Pattern) :提供一个建立一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象建立型模式。抽象工厂模式包含以下角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

咱们使用工厂模式的业务场景是这样的:一个专门作“运动户外”新零售的公司,想要在本身的APP上向用户推荐不一样的服装、鞋帽搭配套装,推荐的策略根据用户性别的不一样有所不一样,男生推荐鞋子和背包,女生推荐外套和裤子;根据产品策略的不一样,又有不一样的推荐版本,好比第一个版本只推荐阿迪达斯的产品,第二个版本则推荐耐克的产品。这个业务场景就很是适合使用“抽象工厂模式”。咱们先来看一下UML类图:

工厂类UML类图实现

是的,使用抽象工厂模式确实有一个缺点:类特别多。可是带来的好处也是很明显的,咱们先来看一下代码实现:

首先是抽象工厂类:

abstract class recommendFactory
{
    public function createRecommendClass($sex) {}
}
复制代码

咱们假设每个版本的推荐都有一个相关的工厂类,他们都继承抽象工厂类:

class concreteRecommendFactoryV1 extends recommendFactory
{
    public function createRecommendClass($sex) {
        switch ($sex) {
            case 'man':
                return new concreteRecommendClassV1man();
            case 'women':
                return new concreteRecommandClassV1women();
        }
    }
}
......
class concreteRecommendFactoryV2 extends recommendFactory
{
    public function createRecommendClass($sex) {
        switch ($sex) {
            case 'man':
                return new concreteRecommendClassV2man();
            case 'women':
                return new concreteRecommendClassV2women();
        }
    }
}
复制代码

工厂类用来生产产品对象,根据用户的性别生产不一样的对象实例,产品对象也都有继承的抽象类,咱们来看一下产品的抽象类:

abstract class recommendClass
{
    public function recommendRun() {}
}
复制代码

而后工厂类生产出来根据产品类获得的产品实例。咱们来分别看一下产品类的内容:

class concreteRecommendClassV1man extends recommendClass
{
    public function recommendRun() {
        return '推荐耐克鞋子和背包';
    }
}
......
class concreteRecommandClassV1women extends recommendClass
{
    public function recommendRun() {
        return '推荐耐克上衣和裤子';
    }
}
......
class concreteRecommendClassV2man extends recommendClass
{
    public function recommendRun() {
        return '推荐阿迪达斯鞋子和背包';
    }
}
......
class concreteRecommendClassV2women extends recommendClass
{
    public function recommendRun() {
        return '推荐阿迪达斯上衣和裤子';
    }
}
复制代码

这样咱们就能够根据不一样的版本,用户不一样的性别来展现不一样的推荐信息了:

include './recommendClass.php';
include './concreteRecommendClassV1man.php';
include './concreteRecommendClassV2man.php';
include './concreteRecommandClassV1women.php';
include './concreteRecommendClassV2women.php';
include './recommendFactory.php';
include './concreteRecommendFactoryV1.php';
include './concreteRecommendFactoryV2.php';

$sex = $_GET['sex'];
$version = $_GET['version'];

switch ($version) {
    case 'version1' :
        $factory = new concreteRecommendFactoryV1();
        break;
    case 'version2' :
        $factory = new concreteRecommendFactoryV2();
}

$recommendClass = $factory->createRecommendClass($sex);
$recommendContent = $recommendClass->recommendRun();
echo $recommendContent.'<br/>';
复制代码

虽然咱们实现逻辑中用到的类比较的多,可是代码的可拓展性很强。再次发布不一样的版本推荐策略时,咱们只要建立相应的工厂,生产对应的类便可。

抽象工厂模式隔离了具体类的生成,因为这种隔离,更换一个具体工厂就变得相对容易。全部的具体工厂都实现了抽象工厂中定义的那些公共接口,所以只需改变具体工厂的实例,就能够在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式能够实现高内聚低耦合的设计目的,所以抽象工厂模式获得了普遍的应用。

2.2 装饰器模式

装饰器模式属于结构型模式,装饰器模式能够动态的给一个对象添加额外的功能,就增长功能来讲,装饰器模式比生成子类更为灵活;它容许向一个现有的对象添加新的功能,同时又不改变其结构。读过Laravel源码的人应该都知道,Laravel中间件(Middleware)的实现就是使用的装饰器模式;Koa.js 最为人所知的基于 洋葱模型 的HTTP中间件处理流程也是装饰器模式。关于Laravel中间件源码的实现,笔者专门整理了一篇博文,感兴趣的读者能够读一下:Lumen中间件源码解析

咱们如今有这样一个需求:如今有一家餐馆,入住美团以后提供外卖服务,经过让顾客选套餐的方式,提供给用户选择的多样性,以此来增长销量。其中套餐能够由:主食、素菜、饮料、荤菜组成,其中主食和素材是基本套餐项,顾客能够再次基础上再选择添加饮料和荤菜,简单列一下菜系:

  • 主食:米饭、馒头
  • 素菜:土豆丝、番茄鸡蛋、炒豆角
  • 饮料:雪碧、可乐、酸梅汤
  • 荤菜:回锅肉、牛排、羊排

明确了需求,咱们来看一下案例中使用到装饰器所用到的UML类图:

装饰器模式UML类图

根据UML类图,咱们来看一下代码实现,首先是添加菜品的接口:

<?php
/**
 * Description: 抽象接口,真实对象和装饰对象具备相同的接口
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */
interface AbstractComponent{
    public function addCategory(array &$dishes, Closure $next);
}
复制代码

一个具体要装饰的对象ConcreteComponent实现了这个接口:

<?php
/**
 * Description: 具体的对象
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class ConcreteComponent implements AbstractComponent
{
    public function addCategory(array &$dishes, Closure $next)
    {
        return function(&$dishes) use ($next) {
            $dishes[] = ['米饭', '馒头'];
            $dishes[] = ['土豆丝', '番茄鸡蛋', '炒豆角'];

            return $next($dishes);
        };
    }
}
复制代码

咱们这里直接定义了主食和素材做为基础套餐,接下来对它进行装饰。 接下来就是定义了装饰器的抽象类了:

<?php
/**
 * Description: 装饰类,继承了Component,从外类来扩展Component类的功能。
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

abstract class AbstractDecorator implements AbstractComponent
{
    private function initCategory() {}
    public function addCategory(array &$dishes, Closure $next) {}
}
复制代码

抽象类中定义了一个私有方法initCategory,用来管理菜系的菜品,好比说素菜有:土豆丝、番茄鸡蛋、炒豆角,未来还可能添加豆腐、海带等其余素菜菜品。addCategory就是装饰器,将菜品添加到用户选择的菜系中。荤菜类和饮料类菜品继承了抽象装饰器:

<?php
/**
 * Description: 荤菜装饰器,为菜品添加荤菜
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class ChivesDecorator extends AbstractDecorator
{
    private function initCategory()
    {
        return ['回锅肉', '牛排', '羊排'];
    }

    public function addCategory(array &$dishes, Closure $next)
    {
        $dishes[] = $this->initCategory();
        return $next($dishes);
    }
}
......
/**
 * Description: 饮料装饰器,为菜品添加类
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-20
 */

class DrinkDecorator
{
    private function initCategory()
    {
        return ['雪碧', '可乐', '酸梅汤'];
    }

    public function addCategory(array &$dishes,Closure $next)
    {
        $dishes[] = $this->initCategory();
        return $next($dishes);
    }
}
复制代码

基本的类都实现了以后,咱们来看一下怎样在基础套餐之上进行装饰吧:

<?php
include __DIR__.'/AbstractComponent.php';
include __DIR__.'/AbstractDecorator.php';
include __DIR__.'/ConcreteComponent.php';
include __DIR__.'/DrinkDecorator.php';
include __DIR__.'/ChivesDecorator.php';

//将用户选好的套餐排列组合
$output = function ($dishes) {
    $items = [];
    $init = array_shift($dishes);
    foreach($init as $item) {
        $items[] = [$item];
    }
    do{
        $elements = array_shift($dishes);
        $temp = [];
        foreach($elements as $element) {
            foreach($items as $item) {
                $item[] = $element;
                $temp[] = $item;
            }
        }
        $items = $temp;
    } while(count($dishes));

    return $items;
};

//组合函数,将装饰器函数一层层包装
function combinationFunc()
{
    return function($stack, $decorators){
        return function (&$dishes) use ($stack, $decorators) {
            return $decorators->addCategory($dishes, $stack);
        };
    };
}

$dishes = [];
$baseCategory = (new ConcreteComponent())->addCategory($dishes, $output);

print '加荤菜和饮料后的套餐组合<br/>';
$addCategoryDecorators = [new DrinkDecorator(), new ChivesDecorator()];
$go = array_reduce(array_reverse($addCategoryDecorators), combinationFunc(), $baseCategory);

var_dump($go($dishes));
复制代码

首先咱们将用户选择的套餐放到数组$dishes中,咱们向套餐中添加基础套餐 $baseCategory = (new ConcreteComponent())->addCategory($dishes, $output);

其中$output是排列组合函数,用户选择套餐以后,对用户选择的菜品进行排列组合,好比用户选择了主食、素材和荤菜,那么咱们就从这三种菜系中各挑出一个菜进行组合造成套餐。提及来容易,可是对二维数组排列组合不是一件简单的事情,笔者这里的方法读者能够参考一下:

$output = function ($dishes) {
    $items = [];
    $init = array_shift($dishes);
    foreach($init as $item) {
        $items[] = [$item];
    }
    do{
        $elements = array_shift($dishes);
        $temp = [];
        foreach($elements as $element) {
            foreach($items as $item) {
                $item[] = $element;
                $temp[] = $item;
            }
        }
        $items = $temp;
    } while(count($dishes));

    return $items;
};
复制代码

接下来咱们用荤菜类和饮料类对基本的套餐类进行装饰:

print '加荤菜和饮料后的套餐组合<br/>';
$addCategoryDecorators = [new DrinkDecorator(), new ChivesDecorator()];
$go = array_reduce(array_reverse($addCategoryDecorators), combinationFunc(), $baseCategory);

var_dump($go($dishes));
复制代码

这样就达到了咱们想要的效果,若是读者对array_reduce函数不是很理解的话能够看一下官方文档。这样一个选择套餐的装饰器就完成了,咱们能够根据用户选择的套餐信息来组合装饰器,好比这里的$addCategoryDecorators是饮料类和荤菜类,未来还能够增长其余用户选择的菜系。这种装饰器模式并无经过继承来拓展基础类套餐,而是经过组合横向拓展了类的能力,后期代码也方便维护,装饰器模式也能够称为责任链模式、管道模式,在项目中也常常会使用到。

2.3 发布/订阅模式

发布/订阅模式(又称为观察者模式,属于行为型模式的一种,它是将行为独立模块化,下降了行为和主体的耦合性。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知全部的观察者对象,使他们可以自动更新本身。熟悉js操做DOM的读者应该知道,为DOM元素绑定一个事件可使用addEventListener方法,其实这就是一种发布/订阅模式,当事件发生时,各个监听的组建就会收到通知,继而产生交互效果。发布/订阅模式在项目中使用的很是多,例如Lumen框架中想要监听数据库的操做,并把数据库的每一次操做都记录到日志当中去,未来能够分析慢查询问题,就可使用以下方法:

\DB::listen(function ($query) {
               ....//这里是对sql语句的操做
            });
复制代码

php底层的不少实现机制也都使用到了发布/订阅模式,好比信号处理、错误处理等。和生产者、消费者模式不一样的是,生产者、消费者每每是在两个进程中进行的,是一种异步处理的策略,发布/订阅模式则是当主体对象发生改变时,实时通知到观察者。

发布/订阅模式如此重要,以致于SPL直接给出了实现方案,咱们本例中也是经过SPL提供的接口SplSubject/SplObserver来实现的,例子也是最普通的:学校中当有新书上架时,将新书上架的消息通知给老师和学生。

咱们先来看一下发布/订阅模式的UML类图:

订阅发布模式UML类图

其中SplSubject类提供添加监听者(attach),删除监听者(detach),通知监听者(notify)三个方法,Book类实现了这个接口,咱们来看一下代码:

<?php
/**
* Description: 订阅与发布模式的实现
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Book implements SplSubject
{
   private $author = null;    //做者
   private $price = null;     //售价
   private $name = null;      //书名

   private $observers = null;

   public function __construct($name, $author, $price)
   {
       $this->name = $name;
       $this->author = $author;
       $this->price = $price;

       //初始化观察者为spl对象存储
       $this->observers = new SplObjectStorage();
   }

   /**
    * 添加观察者
    * @param SplObserver $observer
    */
   public function attach(SplObserver $observer)
   {
       $this->observers->attach($observer);
   }

   /**
    * 删除观察者
    * @param SplObserver $observer
    */
   public function detach(SplObserver $observer)
   {
       $this->observers->detach($observer);
   }

   /**
    * 通知观察者
    */
   public function notify()
   {
       $params = [
           'name' => $this->name,
           'author' => $this->author,
           'price' => $this->price
       ];

       foreach ($this->observers as $observer) {
           $observer->update($this, $params);
       }
   }
}
复制代码

咱们这里是用到了SplObjectStorage对象来存储观察者对象,这样就省去了底层数组的不少操做细节,好比in_array判断观察者对象是否已经存在了,这是一种委托设计模式。接下来咱们再来实现订阅者,订阅者只须要实现SplObserver的update方法便可:

<?php
/**
* Description: 学生类观察者
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Student implements SplObserver
{
   public function update(\Splsubject $subject)
   {
       // TODO: Implement update() method.
       if(func_num_args() == 2) {
           $params = func_get_arg(1);
           echo '学生已经收到',$params['name'],'上架的信息','做者:',$params['author'],'订价:',$params['price'],"<br>";
       }
   }
}
......
class Teacher implements SplObserver
{
   public function update(SplSubject $subject)
   {
       // TODO: Implement update() method.
       if(func_num_args() == 2) {
           $params = func_get_arg(1);
           echo '老师已经收到',$params['name'],'上架的信息','做者:',$params['author'],'订价:',$params['price'],"<br/>";
       }
   }
}
复制代码

学生类和老师类收到新书上架的信息后,会打印出接收通知,update接收一个SplSubject对象,其实在Observer类中接收Subject对象属性的方法更好的是在Subject对象中添加一个getParams的方法,不直接去访问对象的内部属性,这样作设计模式中开闭原则。本例中经过参数接收发布者传递过来的信息,有些取巧,不过也达到了效果。接下来只要给发布者添加监听对象就能够了:

<?php
include __DIR__.'/Book.php';
include __DIR__.'/Student.php';
include __DIR__.'/Teacher.php';

$student = new Student();
$teacher = new Teacher();
$book = new Book('<<钢铁是怎样炼成的>>', '奥斯特洛夫斯基', '79.00');
$book->attach($student);
$book->attach($teacher);

$book->notify();
复制代码

几乎全部的api框架中都会提供这样一种事件发布/订阅机制,Laravel中专门有本身的Event模块的设计,基于此能够实现事件的广播。发布/订阅模式实现相对简单,读者本身也能够进行实现。

2.4 迭代器模式

迭代器模式(Iterator),又叫作游标(Cursor)模式。提供一种方法访问一个容器(Container)对象中各个元素,而又不需暴露该对象的内部细节。 当你须要访问一个聚合对象,并且无论这些对象是什么都须要遍历的时候,就应该考虑使用迭代器模式。另外,当须要对汇集有多种方式遍历时,能够考虑去使用迭代器模式。迭代器模式为遍历不一样的汇集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。 PHP标准库(SPL)中提供了迭代器接口 Iterator,要实现迭代器模式,实现该接口便可。咱们来看一个简单的例子,咱们使用对象从数据库中取到数据以后,想要遍历取出对象中的数据,就可使用迭代器模式,UML类图很是简单:

咱们先来实现一个简单的数据库链接查询类,php7之后移除了mysqli模块,推荐使用PDO操做数据库(笔者认为这也和设计模式有关系,php程序员已经习惯了提到php就想到mysql,而PDO却能够将操做mysql、sqlserver、Oracle其余数据库的操做封住成统一的接口,这是一种适配器的设计模式)。咱们例子使用PDO简单实现一下:

class PdoDB
{
   private static $conn = null;

   //禁止被实例化
   private function __construct()
   {
   }

   private static function connDB()
   {
       return new PDO('mysql:host=localhost;sort=3306;dbname=study;', 'root', 'root');
   }

   /**
    * 获取数据库链接单例
    * @return null|PDO
    */
   public static function getInstance()
   {
       if (is_null(self::$conn)) {
           self::$conn = self::connDB();
       }
       return self::$conn;
   }

   private function __clone()
   {
       // TODO: Implement __clone() method.
       echo 'error!';
   }
}
复制代码

咱们那的PdoDB使用了单例模式实现,单例模式中的构造函数是private,另外当用户对对象进行克隆的时候,应该报错(克隆对象的操做也是一种设计模式,叫原型模式,和单例模式不一样的是:原型模式是建立型模式,是建立新对象,而单例模式的目的是共用一个对象)。接下来咱们实现Users类,其中使用PdoDB操做数据库取出数据,并实现迭代器Iterator:

<?php
/**
* Description: 迭代类
* User: guozhaoran<guozhaoran@cmcm.com>
* Date: 2019-10-27
*/

class Users implements Iterator
{
   protected $data;
   protected $index;

   public function __construct()
   {
       $this->data = PdoDB::getInstance()->query('select * from users')->fetchAll();
   }

   public function current()
   {
       $current = $this->data[$this->index];

       return $current;
   }

   public function next()
   {
       $this->index++;
   }

   public function key()
   {
       return $this->index;
   }

   public function valid()
   {
       return $this->index < count($this->data);
   }

   public function rewind()
   {
       $this->index = 0;
   }
}
复制代码

而后咱们就能够对Users对象进行遍历了:

<?php
/**
 * Description: File basic description here...
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-27
 */
include __DIR__.'/PdoDB.php';
include __DIR__.'/Users.php';

//对数据进行迭代
$users = new Users();
foreach($users as $user) {
    var_dump($user);
}
复制代码

上边的例子很简单;说到迭代器,不得不提一下PHP另一个强大的特性:生成器,生成器是 PHP 5.5 引入的新特性,可是目前貌似不多人用到它。 下面试 PHP 官方文档上对生成器的解释: 生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大下降。 生成器容许你在 foreach 代码块中写代码来迭代一组数据而不须要在内存中建立一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你能够写一个生成器函数,就像一个普通的自定义函数同样, 和普通函数只返回一次不一样的是, 生成器能够根据须要 yield 屡次,以便生成须要迭代的值。

咱们来看一个简单的文件读取的例子:

<?php
/**
 * Description: 生成器读取超大文件
 * User: guozhaoran<guozhaoran@cmcm.com>
 * Date: 2019-10-27
 */
header("content-type:text/html;charset=utf-8");

/*$startMemory = memory_get_usage();
$value = file_get_contents('./test.txt');
$useMemory = memory_get_usage() - $startMemory;
echo '一共占用了',$useMemory,'字节内存';*/

function readTxt()
{
    $handle = fopen('./test.txt', 'rb');

    while (feof($handle)===false) {
        yield fgets($handle);
    }

    fclose($handle);
}

$startMemory = memory_get_usage();
foreach (readTxt() as $key => $value) {
   $lineData = $value;
}

$useMemory = memory_get_usage() - $startMemory;
echo '一共占用了',$useMemory,'字节内存';
复制代码

运行上边案例,对比使用file_get_contents读取文件,咱们看到使用生成器节省了大量的内存,生成器的引入使得程序中的函数达到了中断的效果,实现迭代器也只须要使用yield关键字便可,yield返回的就是一个Iterator对象。总的来讲,迭代器模式在业务场景中不经常使用,可是颇有用,读者若是以前没接触过相似的概念,能够搜集资料学习一下并在项目中使用。

3.小结

设计模式的内容多多少少仍是有些抽象的,它是一把双刃剑,很容易被滥用。不一样的设计模式适用于不一样的业务场景,只有经过大量的经验积累和学习理解,才能将设计模式用的恰到好处。设计模式的最终目的是为了使系统解耦,方便开发者维护代码。设计模式有不少,笔者认为,结合不一样的业务场景尝试使用合理的设计模式解决问题,是最好的学习方式。

相关文章
相关标签/搜索