首页
统计
友链
留言
Search
1
Windows下搭建Laravel开发环境~homestead
2,890 阅读
2
php解答leetcode第7题- 整数反转
2,822 阅读
3
基于php的sphinx和coreseek全文搜索,中文分词(一)
2,687 阅读
4
Laravel 安装 jwt 及基本使用
2,523 阅读
5
php解答leetcode第26题-删除排序数组中的重复项
2,015 阅读
PHP
laravel
svn
nginx
leetcode
算法
Golang
登录
Search
标签搜索
设计模式
leetcode
laravel
sphinx
coreseek
全文搜索
中文分词
compsoer
svn
centos
jwt
homestead
算法
php
yangpanyao
累计撰写
45
篇文章
累计收到
78
条评论
首页
栏目
PHP
laravel
svn
nginx
leetcode
算法
Golang
页面
统计
友链
留言
搜索到
27
篇与
设计模式
的结果
2020-07-25
PHP设计模式-访问者模式
前面我们已经讲了22个设计模式。大部分设计模式的原理和实现都很简单,不过也有例外,比如今天要讲的最后一个设计模式--访问者模式。它可以算是 23 种经典设计模式中最难理解的几个之一。因为它难理解、难实现,应用它会导致代码的可读性、可维护性变差,所以,访问者模式在实际的软件开发中很少被用到,在没有特别必要的情况下,建议你不要使用访问者模式。尽管如此,为了让你以后读到应用了访问者模式的代码的时候,能一眼就能看出代码的设计意图,同时为了整个专栏内容的完整性,我觉得还是有必要给你讲一讲这个模式。除此之外,为了最大化学习效果,我今天不只是单纯地讲解原理和实现,更重要的是,我会手把手带你还原访问者模式诞生的思维过程,让你切身感受到创造一种新的设计模式出来并不是件难事。带你“发明”访问者模式假设我们从网站上爬取了很多资源文件,它们的格式有三种:PDF、PPT、Word。我们现在要开发一个工具来处理这批资源文件。这个工具的其中一个功能是,把这些资源文件中的文本内容抽取出来放到 txt 文件中。如果让你来实现,你会怎么来做呢?实现这个功能并不难,不同的人有不同的写法,我将其中一种代码实现方式贴在这里。其中,ResourceFile 是一个抽象类,包含一个抽象函数 extract2txt()。PdfFile、PPTFile、WordFile 都继承 ResourceFile 类,并且重写了 extract2txt() 方法。在 客户端中,我们可以利用多态特性,根据对象的实际类型,来决定执行哪个方法。ResourceFile.php<?php namespace DesignPatterns\Visitor\v1; abstract class ResourceFile { protected $filePath; public function __construct($filePath) { $this->filePath = $filePath; } abstract public function extract2txt(); }PdfFile.php<?php namespace DesignPatterns\Visitor\v1; class PdfFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } public function extract2txt() { echo "Extract pdf.<br>"; } }PPTFile.php<?php namespace DesignPatterns\Visitor\v1; class PPTFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } //...省略一大坨从PPT中抽取文本的代码... //...将抽取出来的文本保存在跟filePath同名的.txt文件中... public function extract2txt() { echo "Extract ppt.<br>"; } }WordFile.php<?php namespace DesignPatterns\Visitor\v1; class WordFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } public function extract2txt() { echo "Extract word.<br>"; } }Client.php<?php namespace DesignPatterns\Visitor\v1; require dirname(__DIR__) . '/../vendor/autoload.php'; class Client { private $resourceFiles = []; public function run() { $this->listAllResourceFiles(); foreach ($this->resourceFiles as $value) { $value->extract2txt(); } } public function listAllResourceFiles() { //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile) $this->resourceFiles[] = new PdfFile('a.pdf'); $this->resourceFiles[] = new PPTFile('b.ppt'); $this->resourceFiles[] = new WordFile('c.word'); } } $worker = new Client(); $worker->run(); // 运行结果是: //Extract pdf. //Extract ppt. //Extract word.如果工具的功能不停地扩展,不仅要能抽取文本内容,还要支持压缩、提取文件元信息(文件名、大小、更新时间等等)构建索引等一系列的功能,那如果我们继续按照上面的实现思路,就会存在这样几个问题:违背开闭原则,添加一个新的功能,所有类的代码都要修改;虽然功能增多,每个类的代码都不断膨胀,可读性和可维护性都变差了;把所有比较上层的业务逻辑都耦合到 PdfFile、PPTFile、WordFile 类中,导致这些类的职责不够单一,变成了大杂烩。针对上面的问题,我们常用的解决方法就是拆分解耦,把业务操作跟具体的数据结构解耦,设计成独立的类。这里我们按照访问者模式的演进思路来对上面的代码进行重构。重构之后的代码如下所示。abstract class ResourceFile { protected $filePath; public function __construct($filePath) { $this->filePath = $filePath; } abstract public function acceptExtractor(Extractor $extractor); } class PdfFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } public function acceptExtractor(Extractor $extractor) { $extractor->extract2txtPdfFile($this); } } //...PPTFile、WordFile跟PdfFile类似,这里就省略了... class Extractor { public function extract2txtPPTFile(PPTFile $PPTFile) { echo "Extract ppt.<br>"; } public function extract2txtPdfFile(PdfFile $pdfFile) { echo "Extract pdf.<br>"; } public function extract2txtWordFile(WordFile $wordFile) { echo "Extract word.<br>"; } } class Client { private $resourceFiles = []; public function run() { $this->listAllResourceFiles(); foreach ($this->resourceFiles as $value) { $value->acceptExtractor(new Extractor()); } } public function listAllResourceFiles() { //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile) $this->resourceFiles[] = new PdfFile('a.pdf'); $this->resourceFiles[] = new PPTFile('b.ppt'); $this->resourceFiles[] = new WordFile('c.word'); } } $worker = new Client(); $worker->run(); //输出结果 //Extract pdf. //Extract ppt. //Extract word.我们把抽取文本的操作抽取出来独立成一个Extract类 。在程序执行循环内时,根据多态特性,程序会调用实际类型的 acceptExtractor 方法,比如 PdfFile 的 acceptExtractor 方法,而PdfFile中的 this 类型是 PdfFile 的,在编译的时候就确定了,所以会调用 extractor 的 extract2txtPdfFile(PdfFile pdfFile) 这个方法。这个实现思路是不是很有技巧?这是理解访问者模式的关键所在,也是我之前所说的访问者模式不好理解的原因。现在,如果要继续添加新的功能,比如前面提到的压缩功能,根据不同的文件类型,使用不同的压缩算法来压缩资源文件,那我们该如何实现呢?我们需要实现一个类似 Extractor 类的新类 Compressor 类,在其中定义三个同样的方法,实现对不同类型资源文件的压缩。除此之外,我们还要在每个资源文件类中定义新的 accept 方法。具体的代码如下所示:abstract class ResourceFile { protected $filePath; public function __construct($filePath) { $this->filePath = $filePath; } abstract public function acceptExtractor(Extractor $extractor); abstract public function acceptCompressor(Compressor $compressor); } class PdfFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } public function acceptExtractor(Extractor $extractor) { $extractor->extract2txtPdfFile($this); } public function acceptCompressor(Compressor $compressor) { $compressor->compressPdfFile($this); } } //...PPTFile、WordFile跟PdfFile类似,这里就省略了... //...Extractor代码不变 class Compressor { public function compressPPTFile(PPTFile $PPTFile) { echo "compress ppt.<br>"; } public function compressPdfFile(PdfFile $pdfFile) { echo "compress pdf.<br>"; } public function compressWordFile(WordFile $wordFile) { echo "compress word.<br>"; } } class Client { private $resourceFiles = []; public function run() { $this->listAllResourceFiles(); foreach ($this->resourceFiles as $value) { $value->acceptExtractor(new Extractor()); } foreach ($this->resourceFiles as $value) { $value->acceptCompressor(new Compressor()); } } public function listAllResourceFiles() { //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile) $this->resourceFiles[] = new PdfFile('a.pdf'); $this->resourceFiles[] = new PPTFile('b.ppt'); $this->resourceFiles[] = new WordFile('c.word'); } } $worker = new Client(); $worker->run(); //运行结果 //Extract pdf. //Extract ppt. //Extract word. //compress pdf. //compress ppt. //compress word.上面代码还存在一些问题,添加一个新的业务,还是需要修改每个资源文件类,违反了开闭原则。针对这个问题,我们抽象出来一个 Visitor 接口,包含是三个命名非常通用的 visit() 方法,分别处理三种不同类型的资源文件。具体做什么业务处理,由实现这个 Visitor 接口的具体的类来决定,比如 Extractor 负责抽取文本内容,Compressor 负责压缩。当我们新添加一个业务功能的时候,资源文件类不需要做任何修改,只需要修改 客户端的代码就可以了。按照这个思路我们可以对代码进行重构,重构之后的代码如下所示:abstract class ResourceFile { protected $filePath; public function __construct($filePath) { $this->filePath = $filePath; } abstract public function acceptExtractor(Visitor $visitor); abstract public function acceptCompressor(Visitor $visitor); } class PdfFile extends ResourceFile { public function __construct($filePath) { parent::__construct($filePath); } public function acceptExtractor(Visitor $visitor) { $visitor->visitPdfFile($this); } public function acceptCompressor(Visitor $visitor) { $visitor->visitPdfFile($this); } } //...PPTFile、WordFile跟PdfFile类似,这里就省略了... interface Visitor { public function visitPPTFile(PPTFile $PPTFile); public function visitPdfFile(PdfFile $pdfFile); public function visitWordFile(WordFile $wordFile); } class Extractor implements Visitor { public function visitPPTFile(PPTFile $PPTFile) { echo 'Extract ppt.<br>'; } public function visitPdfFile(PdfFile $pdfFile) { echo 'Extract ppt.<br>'; } public function visitWordFile(WordFile $wordFile) { echo 'Extract ppt.<br>'; } } class Compressor implements Visitor { public function visitPPTFile(PPTFile $PPTFile) { echo "compress ppt.<br>"; } public function visitPdfFile(PdfFile $pdfFile) { echo "compress pdf.<br>"; } public function visitWordFile(WordFile $wordFile) { echo "compress word.<br>"; } } class Client { private $resourceFiles = []; public function run() { $this->listAllResourceFiles(); foreach ($this->resourceFiles as $value) { $value->acceptExtractor(new Extractor()); } foreach ($this->resourceFiles as $value) { $value->acceptCompressor(new Compressor()); } } public function listAllResourceFiles() { //...根据后缀(pdf/ppt/word)由工厂方法创建不同的类对象(PdfFile/PPTFile/WordFile) $this->resourceFiles[] = new PdfFile('a.pdf'); $this->resourceFiles[] = new PPTFile('b.ppt'); $this->resourceFiles[] = new WordFile('c.word'); } } $worker = new Client(); $worker->run();重新来看访问者模式刚刚我带你一步一步还原了访问者模式诞生的思维过程,现在,我们回过头来总结一下,这个模式的原理和代码实现。访问者者模式的英文翻译是 Visitor Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:Allows for one or more operation to be applied to a set of objects at runtime, decoupling the operations from the object structure.翻译成中文就是:允许一个或者多个操作应用到一组对象上,解耦操作和对象本身。定义比较简单,结合前面的例子不难理解,我就不过多解释了。对于访问者模式的代码实现,实际上,在上面例子中,经过层层重构之后的最终代码,就是标准的访问者模式的实现代码。这里,我又总结了一张类图,贴在了下面,你可以对照着前面的例子代码一块儿来看一下。最后,我们再来看下,访问者模式的应用场景。一般来说,访问者模式针对的是一组类型不同的对象(PdfFile、PPTFile、WordFile)。不过,尽管这组对象的类型是不同的,但是,它们继承相同的父类(ResourceFile)或者实现相同的接口。在不同的应用场景下,我们需要对这组对象进行一系列不相关的业务操作(抽取文本、压缩等),但为了避免不断添加功能导致类(PdfFile、PPTFile、WordFile)不断膨胀,职责越来越不单一,以及避免频繁地添加功能导致的频繁代码修改,我们使用访问者模式,将对象与操作解耦,将这些业务操作抽离出来,定义在独立细分的访问者类(Extractor、Compressor)中。总结访问者模式允许一个或者多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。对于访问者模式,学习的主要难点在代码实现。而代码实现比较复杂的主要原因是,方法在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个方法,是在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。正是因为代码实现难理解,所以,在项目中应用这种模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种模式。参考文章:设计模式之美- 访问者模式(上):手把手带你还原访问者模式诞生的思维过程github示例:https://github.com/yangpanyao/design-patterns/tree/master/Visitor
2020年07月25日
1,815 阅读
7 评论
1 点赞
2020-07-23
PHP设计模式-中介者模式
中介者模式的英文翻译是 Mediator Design Pattern。在 GoF 中的《设计模式》一书中,它是这样定义的:Mediator pattern defines a separate (mediator) object that encapsulates the interaction between a set of objects and the objects delegate their interaction to a mediator object instead of interacting with each other directly.翻译成中文就是:中介者模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。实际上,中介者模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。中介者模式的结构与实现结构抽象中介者 (Mediator),抽象中介者角色定义统一的接口用于个同时角色之间的通信。具体中介者(Concrete Mediator)具体中介者角色通过协调各同事角色实现协作行为。为此它要知道并引用各个同事角色。抽象同事类(Colleague),主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能。具体同事类(Concrete Colleague)每个具体同事类只知道自己的行为,而不了解其他同事的情况,但他们都认识中介者对象。需要与其他同事对象交互时,就通知中介对象,中介对象会负责后续的交互。代码示例Mediator.php<?php namespace DesignPatterns\Mediator\example1; abstract class Mediator { //定义一个抽象的发送消息方法,得到同事对象和发送信息 abstract public function Send(string $message, Colleague $colleague); }Colleague.php<?php namespace DesignPatterns\Mediator\example1; abstract class Colleague { protected $mediator; //构造方法,得到中介者对象 public function __construct(Mediator $mediator) { $this->mediator = $mediator; } }ConcreteMediator.php<?php namespace DesignPatterns\Mediator\example1; class ConcreteMediator extends Mediator { private $Colleague1; private $Colleague2; //需要了解所有的具体同事对象 /** * @param mixed $colleague1 */ public function setColleague1($colleague1): void { $this->Colleague1 = $colleague1; } /** * @param mixed $colleague2 */ public function setColleague2($colleague2): void { $this->Colleague2 = $colleague2; } //重写发送消息的方法,根据对象作出选择的判断,通知对象 public function Send(string $message, Colleague $colleague) { if ($this->Colleague1 == $colleague){ $this->Colleague2->Notify($message); }else{ $this->Colleague1->Notify($message); } } }ConcreteColleague1.php<?php namespace DesignPatterns\Mediator\example1; class ConcreteColleague1 extends Colleague { public function __construct(Mediator $mediator) { parent::__construct($mediator); } public function Send(string $message) { $this->mediator->Send($message,$this);//发送消息时通常是中介者发送出去的 } public function Notify(string $message) { echo ' 同事1得到消息:'.$message."<br>"; } }ConcreteColleague2.php<?php namespace DesignPatterns\Mediator\example1; class ConcreteColleague2 extends Colleague { public function __construct(Mediator $mediator) { parent::__construct($mediator); } public function Send(string $message) { $this->mediator->Send($message,$this); } public function Notify(string $message) { echo ' 同事2得到消息:'.$message."<br>"; } }Client.php<?php namespace DesignPatterns\Mediator\example1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $m = new ConcreteMediator(); //让两个具体同事类认识中介者对象 $c1 = new ConcreteColleague1($m); $c2 = new ConcreteColleague2($m); //让中介者认识各个具体同事类对象 $m->setColleague1($c1); $m->setColleague2($c2); //具体同事类对象的发送信息都是通过中介者转发 $c1->Send("吃过饭了吗?"); $c2->Send("没有呢,你打算请客吗?"); } } $worker = new Client(); $worker->run();运行结果:由于有了Mediator,使得ConcreteColleague1和ConcreteColleague2在发送消息和接收消息时其实是通过中介者来完成的,这就减少了它们之间的耦合度。示例中介者模式的思想在现实生活中也很常见,比如说交换机。没有交换机存在的时代,每个电话之间都需要电话线连接才能进行通话。如果一个台电话要和其它100台电话通话,那么它就必须要有100条电话线与其它100个电话连接。后来为了解决这种麻烦,交换机出现了。每个电话只需连入交换机,通话时。只需构建一条电话-交换机-电话的链路,就可以进行通话。所以现在我们的电话理论上可以同世界上任何一台电话通话,但是只需一条电话线。当然现在用电话的人少了,但是手机呀,计算机网络的实现也是在传统通信网的设计上进行演进的。其实交换机对应的就是中介者模式的中介者,而电话机就是中介者中的同事。下面,就让我们用代码来实现这个思想。Colleague.php<?php namespace DesignPatterns\Mediator\example2; /** * 抽象同事类-电话机 * Class Colleague * @package DesignPatterns\Mediator\example2 */ abstract class Colleague { protected $mediator; //用于存放中介者 abstract public function sendMsg($num,$msg); abstract public function receiveMsg($msg); /** * 设置中介者对象 * @param mixed $mediator */ final public function setMediator($mediator): void { $this->mediator = $mediator; } }PhoneColleague.php<?php namespace DesignPatterns\Mediator\example2; //具体同事--固话 class PhoneColleague extends Colleague { public function sendMsg($num, $msg) { echo '固话--发出声音:'.$msg."<br>"; $this->mediator->operation($num,$msg); } public function receiveMsg($msg) { echo '固话--接收声音:'.$msg."<br>"; } }Colleague.php<?php namespace DesignPatterns\Mediator\example2; //具体同事--手机 class TelephoneColleague extends Colleague { public function sendMsg($num, $msg) { echo '手机--发出声音:'.$msg."<br>"; $this->mediator->operation($num,$msg); } public function receiveMsg($msg) { echo '手机响铃-------<br>'; echo '手机--接收声音:'.$msg."<br>"; } }Mediator.php<?php namespace DesignPatterns\Mediator\example2; /** * 交换机 中介者角色 * 当只需要一个具体中介者时,Mediator 和 ConcreteMediator可以合二为一 * Class Mediator * @package DesignPatterns\Mediator\example2 */ class Mediator { protected $colleague =[]; //交换机业务处理 public function operation($num, $message) { if (!array_key_exists($num,$this->colleague)) { echo "交换机内没有此号码信息,无法通话".PHP_EOL; }else{ $this->colleague[$num]->receiveMsg($message); } } //注册号码 public function register($num,Colleague $colleague) { if (!in_array($colleague,$this->colleague)) { $this->colleague[$num] = $colleague; } $colleague->setMediator($this); } }Client.php<?php namespace DesignPatterns\Mediator\example2; require dirname(__DIR__).'/../vendor/autoload.php'; /** * Class Client * @package DesignPatterns\Mediator\example2 */ class Client { public function run() { //实例化固话 $phone = new PhoneColleague(); $telePhone = new TelephoneColleague(); $mediator = new Mediator(); //注册号码 $mediator->register('6668688',$phone); $mediator->register('15612341234',$telePhone); //通话 $phone->sendMsg('15612341234','hello world'); $telePhone->sendMsg('6668688','请讲普通话'); $telePhone->sendMsg('6668688','请讲普通话'); } } $worker = new Client(); $worker->run();运行结果:中介者模式的优缺点中介者模式的优点首先是Mediator的出现减少了各个colleague的耦合,使得可以独立的改变和复用各个colleague类和mediator使得任何colleague的改变都不会影响到其他colleague而只与mediator发生变化。其次,由于把对象协作进行了抽象,把中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象从对象各自本身的行为转移到他们之间的交互上来,也就是站在一个更宏观的角度去看待系统。中介者模式的缺点,首先具体中介者类ConcreteMediator可能会因为ConcreteColleague的越来越多耳边的非常复杂反而不容易维护了。由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂化。中介模式 VS 观察者模式我们前面提到,中介模式也是为了解耦对象之间的交互,所有的参与者都只与中介进行交互。而观察者模式中的消息队列,就有点类似中介模式中的“中介”,观察者模式的中观察者和被观察者,就有点类似中介模式中的“参与者”。那问题来了:中介模式和观察者模式的区别在哪里呢?什么时候选择使用中介模式?什么时候选择使用观察者模式呢?在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。而中介模式正好相反。只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,前面也讲到,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类,通过先后调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。总结中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。中介者模式很容易在系统中应用,也很容易在系统中误用,当系统出现‘多对多’交互复杂的对象群时,不要急于使用中介者模式,而要先看看我们的系统在设计上是否合理。参考资料: 设计模式之美-中介模式:什么时候用中介模式?什么时候用观察者模式?github示例:https://github.com/yangpanyao/design-patterns/tree/master/Mediator
2020年07月23日
1,566 阅读
1 评论
0 点赞
2020-07-20
PHP设计模式-解释器模式
上篇文章,我们讲了命令模式。命令模式将请求封装成对象,方便作为函数参数传递和赋值给变量。它主要的应用场景是给命令的执行附加功能,换句话说,就是控制命令的执行,比如,排队、异步、延迟执行命令、给命令执行记录日志、撤销重做命令等等。总体上来讲,命令模式的应用范围并不广。今天,我们来学习解释器模式,它用来描述如何构建一个简单的“语言”解释器。比起命令模式,解释器模式更加小众,只在一些特定的领域会被用到,比如编译器、规则引擎、正则表达式。所以,解释器模式也不是我们学习的重点,你稍微了解一下就可以了。解释器模式的原理和实现解释器模式的英文翻译是 Interpreter Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.翻译成中文就是:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。看了定义,你估计会一头雾水,因为这里面有很多我们平时开发中很少接触的概念,比如“语言”“语法”“解释器”。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。要想了解“语言”表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。为了让你更好地理解定义,我举一个比较贴近生活的例子来解释一下。实际上,理解这个概念,我们可以类比中英文翻译。我们知道,把英文翻译成中文是有一定规则的。这个规则就是定义中的“语法”。我们开发一个类似 Google Translate 这样的翻译器,这个翻译器能够根据语法规则,将输入的中文翻译成英文。这里的翻译器就是解释器模式定义中的“解释器”。刚刚翻译器这个例子比较贴近生活,现在,我们再举个更加贴近编程的例子。假设我们定义了一个新的加减乘除计算“语言”,语法规则如下:运算符只包含加、减、乘、除,并且没有优先级的概念;表达式(也就是前面提到的“句子”)中,先书写数字,后书写运算符,空格隔开;按照先后顺序,取出两个数字和一个运算符计算结果,结果重新放入数字的最头部位置,循环上述过程,直到只剩下一个数字,这个数字就是表达式最终的计算结果。我们举个例子来解释一下上面的语法规则。比如" 8 3 2 4 - + *" 这样一个表达式,我们按照上面的语法规则来处理,取出数字“8 3”和“-”运算符,计算得到 5,于是表达式就变成了“ 5 2 4 + * ”。然后,我们再取出“ 5 2 ”和“ + ”运算符,计算得到 7,表达式就变成了“ 7 4 * ”。最后,我们取出“ 7 4”和 “ * ” 运算符,最终得到的结果就是 28。看懂了上面的语法规则,我们将它用代码实现出来,如下所示。代码非常简单,用户按照上面的规则书写表达式,传递给 interpret() 函数,就可以得到最终的计算结果。<?php namespace DesignPatterns\Interpreter\v1; /** * Class ExpressionInterpreter * @package DesignPatterns\Interpreter\example1 */ class ExpressionInterpreter { private $numbers = []; public function interpret(string $expression) { $elements = explode(" ",$expression); $length = count($elements); for ($i = 0; $i < ($length +1)/2;$i++) { $this->numbers[] = $elements[$i]; } for ($i= ($length+1)/2; $i< $length;$i++) { $operator = $elements[$i]; if (!in_array($operator,array('+','-','*','/'))) { throw new \Exception("表达式无效"); } $number1 = array_shift($this->numbers); $number2 = array_shift($this->numbers); $result = 0; if ($elements[$i] == '+') { $result = $number1 + $number2; }elseif ($elements[$i] == '-') { $result = $number1 - $number2; }elseif ($elements[$i] == '*') { $result = $number1 * $number2; }elseif ($elements[$i] == '/') { $result = $number1 / $number2; } array_unshift($this->numbers,$result); } if (count($this->numbers) != 1) { throw new \Exception("表达式无效"); } return array_pop($this->numbers); } } //clientCode $str = "8 3 2 4 - + *"; $e = new ExpressionInterpreter(); echo $e->interpret($str); //结果 28在上面的代码实现中,语法规则的解析逻辑都集中在一个函数中,对于简单的语法规则的解析,这样的设计就足够了。但是,对于复杂的语法规则的解析,逻辑复杂,代码量多,所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中。该怎么拆分呢?我们可以借助解释器模式。解释器模式的代码实现比较灵活,没有固定的模板。我们前面也说过,应用设计模式主要是应对代码的复杂性,实际上,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。利用解释器模式,我们把解析的工作拆分到 NumberExpression、AdditionExpression、SubstractionExpression、MultiplicationExpression、DivisionExpression 这样五个解析类中。按照这个思路,我们对代码进行重构,重构之后的代码如下所示。当然,因为加减乘除表达式的解析比较简单,利用解释器模式的设计思路,看起来有点过度设计。不过呢,这里我主要是为了解释原理,你明白意思就好,不用过度细究这个例子。Expression.php<?php namespace DesignPatterns\Interpreter\v2; interface Expression { public function interpret(); }NumberExpression.php<?php namespace DesignPatterns\Interpreter\v2; class NumberExpression implements Expression { private $number; public function __construct($number) { $this->number = $number; } public function interpret() { return $this->number; } }AdditionExpression.php<?php namespace DesignPatterns\Interpreter\v2; class AdditionExpression implements Expression { private $exp1; private $exp2; public function __construct(Expression $exp1,Expression $exp2) { $this->exp1 = $exp1; $this->exp2 = $exp2; } public function interpret() { return $this->exp1->interpret() + $this->exp2->interpret(); } }SubstractionExpression.php<?php namespace DesignPatterns\Interpreter\v2; class SubstractionExpression implements Expression { private $exp1; private $exp2; public function __construct(Expression $exp1,Expression $exp2) { $this->exp1 = $exp1; $this->exp2 = $exp2; } public function interpret() { return $this->exp1->interpret() - $this->exp2->interpret(); } }MultiplicationExpression.php<?php namespace DesignPatterns\Interpreter\v2; class MultiplicationExpression implements Expression { private $exp1; private $exp2; public function __construct(Expression $exp1,Expression $exp2) { $this->exp1 = $exp1; $this->exp2 = $exp2; } public function interpret() { return $this->exp1->interpret() * $this->exp2->interpret(); } }DivisionExpression.php<?php namespace DesignPatterns\Interpreter\v2; class DivisionExpression implements Expression { private $exp1; private $exp2; public function __construct(Expression $exp1,Expression $exp2) { $this->exp1 = $exp1; $this->exp2 = $exp2; } public function interpret() { return $this->exp1->interpret() / $this->exp2->interpret(); } }ExpressionInterpreter.php<?php namespace DesignPatterns\Interpreter\v2; require dirname(__DIR__).'/../vendor/autoload.php'; class ExpressionInterpreter { private $numbers = []; public function interpret(string $expression) { $elements = explode(" ",$expression); $length = count($elements); for ($i = 0; $i < ($length +1)/2;$i++) { $this->numbers[] = new NumberExpression($elements[$i]); } for ($i= ($length+1)/2; $i< $length;$i++) { $operator = $elements[$i]; if (!in_array($operator,array('+','-','*','/'))) { throw new \Exception("表达式无效"); } $exp1 = array_shift($this->numbers); $exp2 = array_shift($this->numbers); $combinedExp = null; if ($elements[$i] == '+') { $combinedExp = new AdditionExpression($exp1,$exp2); }elseif ($elements[$i] == '-') { $combinedExp = new SubstractionExpression($exp1,$exp2); }elseif ($elements[$i] == '*') { $combinedExp = new MultiplicationExpression($exp1,$exp2); }elseif ($elements[$i] == '/') { $combinedExp = new DivisionExpression($exp1,$exp2); } $result = $combinedExp->interpret(); array_unshift($this->numbers,new NumberExpression($result)); } if (count($this->numbers) != 1) { throw new \Exception("表达式无效"); } return array_pop($this->numbers)->interpret(); } } //ClientCode $str = "8 3 2 4 - + *"; $e = new ExpressionInterpreter(); echo $e->interpret($str); //结果28总结解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。实际上,这里的“语言”不仅仅指我们平时说的中、英、日、法等各种语言。从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。要想了解“语言”要表达的信息,我们就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写“句子”(专业点的叫法应该是“表达式”),阅读者根据语法规则来阅读“句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来实现根据语法规则解读“句子”的解释器。解释器模式的代码实现比较灵活,没有固定的模板。我们前面说过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。参考文章 :解释器模式:如何设计实现一个自定义接口告警规则功能?github示例 :https://github.com/yangpanyao/design-patterns/tree/master/Interpreter
2020年07月20日
1,230 阅读
1 评论
0 点赞
2020-07-19
PHP设计模式-命令模式
命令模式的英文翻译是 Command Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.翻译成中文就是下面这样。为了帮助你理解,我对这个翻译稍微做了补充和解释,也一起放在了下面的括号中。命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是我们所说的命令模式。命令模式结构与代码实现结构命令角色(Command):声明执行操作的抽象类或接口。具体命令角色(Concrete Command)将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。请求者角色(Invoker):调用命令对象执行这个请求。接收者角色(Receiver):知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。客户角色(Client):创建一个具体命令对象(并可以设定它的接收者)。代码实现Command.php<?php namespace DesignPatterns\Command\example1; interface Command { public function execute(); }ConcreteCommand.php<?php namespace DesignPatterns\Command\example1; class ConcreteCommand implements Command { private $receiver; public function __construct($receiver) { $this->receiver = $receiver; } public function execute() { $this->receiver->action(); } }Invoker.php<?php namespace DesignPatterns\Command\example1; class Invoker { private $command; /** * @param mixed $command */ public function setCommand(Command $command) { $this->command = $command; } public function ExecuteCommand() { $this->command->execute(); } }Receiver.php<?php namespace DesignPatterns\Command\example1; class Receiver { public function action() { echo '执行请求'; } }Client.php<?php namespace DesignPatterns\Command\example1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $receiver = new Receiver(); $command = new ConcreteCommand($receiver); $invoker = new Invoker(); $invoker->setCommand($command); $invoker->ExecuteCommand(); } } $worker = new Client(); $worker->run();命令模式的优点:第一,它能较容易地设计一个命令队列;第二,在需要的情况下,可以较容易地将命令记入日志;第三,允许接收请求的一方决定是否要否决请求。第四,可以容易地实现对请求的撤销和重做;第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。其实还有最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。示例电视机遥控器就是一个典型的命令模式应用实例, 电视机是请求的接收者,遥控器上有一些按钮,不同的按钮对应电视机的不同操作。Tv.php<?php namespace DesignPatterns\Command\example2; /** * 命令接收者 * Class Tv * @package DesignPatterns\Command\example2 */ class Tv { public $curr_channel=0; /** * 打开电视机 */ public function turnOn() { echo "The television is on."."<br/>"; } /** * 关闭电视机 */ public function turnOff() { echo "The television is off."."<br/>"; } /**切换频道 * @param $channel 频道 */ public function turnChannel($channel) { $this->curr_channel=$channel; echo "This TV Channel is ".$this->curr_channel."<br/>"; } }Command.php<?php namespace DesignPatterns\Command\example2; /** * 执行命令的接口 * Interface Command * @package DesignPatterns\Command\example2 */ interface Command { public function execute(); }CommandOn.php<?php namespace DesignPatterns\Command\example2; /** * 开机命令 * Class CommandOn * @package DesignPatterns\Command\example2 */ class CommandOn implements Command { private $tv; public function __construct($tv) { $this->tv=$tv; } public function execute() { $this->tv->turnOn(); } }CommandOff.php<?php namespace DesignPatterns\Command\example2; /** * 关机命令 * Class CommandOff * @package DesignPatterns\Command\example2 */ class CommandOff implements Command { private $tv; public function __construct($tv) { $this->tv=$tv; } public function execute() { $this->tv->turnOff(); } }CommandChannel.php<?php namespace DesignPatterns\Command\example2; /** * 切换频道命令 * Class CommandChannel * @package DesignPatterns\Command\example2 */ class CommandChannel implements Command { private $tv; private $channel; public function __construct($tv,$channel) { $this->tv=$tv; $this->channel=$channel; } public function execute() { $this->tv->turnChannel($this->channel); } }Control.php<?php namespace DesignPatterns\Command\example2; /** * 遥控器 命令发起者 * Class Control * @package DesignPatterns\Command\example2 */ class Control { private $_onCommand; private $_offCommand; private $_changeChannel; public function __construct($on,$off,$channel) { $this->_onCommand = $on; $this->_offCommand = $off; $this->_changeChannel = $channel; } public function turnOn() { $this->_onCommand->execute(); } public function turnOff() { $this->_offCommand->execute(); } public function changeChannel() { $this->_changeChannel->execute(); } }Client.php<?php namespace DesignPatterns\Command\example2; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { // 命令接收者 $myTv = new Tv(); // 开机命令 $on = new CommandOn($myTv); // 关机命令 $off = new CommandOff($myTv); // 频道切换命令 $channel = new CommandChannel($myTv, 2); // 命令控制对象 $control = new Control($on, $off, $channel); // 开机 $control->turnOn(); // 切换频道 $control->changeChannel(); // 关机 $control->turnOff(); } } $worker = new Client(); $worker->run();运行结果总结命令模式,将 一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。对请求派对或记录请求日志,以及日志可撤销的操作。命令模式的优点:第一,它能较容易地设计一个命令队列;第二,在需要的情况下,可以较容易地将命令记入日志;第三,允许接收请求的一方决定是否要否决请求。第四,可以容易地实现对请求的撤销和重做;第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。其实还有最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。是否碰到类似情况就一定要实现命令模式呢? 这就不一定了,比如命令模式支持撤销/恢复操作功能,但你还不清楚是否需要这个功能时,你要不要实现命令模式?”其实应该是不要实现。敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。代码示例:https://github.com/yangpanyao/design-patterns/tree/master/Command
2020年07月19日
1,518 阅读
0 评论
0 点赞
2020-07-17
PHP设计模式-备忘录模式
备忘录(Memento)模式,也叫快照(Snapshot)模式,英文翻译是 Memento Design Pattern。在 GoF 的《设计模式》一书中,备忘录模式是这么定义的:Captures and externalizes an object’s internal state so that it can be restored later, all without violating encapsulation.翻译成中文就是:在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。我们通过一个例子来了解备忘录模式,相信大家应该都玩过一些一些含有存档的游戏吧,例如红警,魂斗罗等,每打完一个关卡后我们都会进行存档,防止下一关没有通过导致我们要从头开始。假设我们现在正在游戏的某个场景,游戏角色有生命力、攻击力、防御力等等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗前。这个代码应该如何实现呢,最简单的我们可以新建一个类用来储存角色的生命力,攻击力,防御力等数据。代码如下:<?php namespace DesignPatterns\Memento\v1; class GameRole { private $vit;//生命力 private $atk;//攻击力 private $def;//防御力 /** * @return mixed */ public function getVit() { return $this->vit; } /** * @param mixed $vit */ public function setVit($vit): void { $this->vit = $vit; } /** * @return mixed */ public function getAtk() { return $this->atk; } /** * @param mixed $atk */ public function setAtk($atk): void { $this->atk = $atk; } /** * @return mixed */ public function getDef() { return $this->def; } /** * @param mixed $def */ public function setDef($def): void { $this->def = $def; } //角色状态显示 public function StateDisplay() { echo "角色当前的状态:<br>体力:".$this->vit, "<br>攻击力: ".$this->atk,"<br>防御力: ".$this->def."<br>"; } //获得初始状态 public function GetInitState(){ $this->vit = 100; $this->atk = 100; $this->def = 100; } //战斗 public function Fight(){ $this->vit = 0; $this->atk = 0; $this->def = 0; } }运行Client.php<?php namespace DesignPatterns\Memento\v1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { //大战boss前 获得初始角色状态 $gameRole = new GameRole(); $gameRole->GetInitState(); $gameRole->StateDisplay(); //保存进度 通过游戏角色的新实例,来保存进度 $backup = new GameRole(); $backup->setVit($gameRole->getVit()); $backup->setAtk($gameRole->getAtk()); $backup->setDef($gameRole->getDef()); //大战boss损耗严重 所有数据损耗为0 $gameRole->Fight(); $gameRole->StateDisplay(); //恢复之前状态 $gameRole->setVit($backup->getVit()); $gameRole->setAtk($backup->getAtk()); $gameRole->setDef($backup->getDef()); $gameRole->StateDisplay(); } } $worker = new Client(); $worker->run();运行结果:这样的写法,确实是实现了我们的要求,但是问题也很多。主要的问题在于这客户端的调用。下面这一段有问题,因为这样写就把整个游戏角色的细节暴露给了客户端,你的客户端的职责就太大了,需要知道游戏角色的生命力、攻击力、防御力这些细节,还要对它进行‘备份’。以后需要增加新的数据,例如增加‘ 魔法力’或修改现有的某种力,例如‘生命力’改为‘经验值’, 这部分就一定要修改了。同样的道理也存在于恢复时的代码。 $backup = new GameRole(); $backup->setVit($gameRole->getVit()); $backup->setAtk($gameRole->getAtk()); $backup->setDef($gameRole->getDef()); $gameRole->setVit($backup->getVit()); $gameRole->setAtk($backup->getAtk()); $gameRole->setDef($backup->getDef());显然,我们希望的是把这些‘游戏角色’的存取状态细节封装起来,而且最好是封装在外部的类当中。以体现职责分离。这个就需要用到我们今天所要讲的备忘录模式了,我们一起来看看备忘录模式的结构与代码实现。备忘录模式的结构与实现结构Originator (发起人):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。Memento (备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker 只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator 能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。就刚才的例子,‘ 游戏角色’类其实就是一个Originator,而我们用了同样的‘游戏角色’实例‘备份’来做备忘录,这在当需要保存全部信息时,是可以考虑的,而用clone的方式来实现Memento的状态保存可能是更好的办法,但是如果是这样的话,使得我们相当于对上层应用开放了Originator的全部( public)接口,这对于保存备份有时候是不合适的。那如果我们不需要保存全部的信息以备使用时,怎么办? 这或许是很可能发生的情况,我们需要保存的如果并不是全部信息,而只是部分,那么就应该有一个独立的备忘录类Memento,它只拥有需要保存的信息的属性。代码实现Originator.php<?php namespace DesignPatterns\Memento\example; /** * 发起人类 * Class Originator * @package DesignPatterns\Memento\example */ class Originator { private $state; /** * @param mixed $state */ public function setState($state): void { $this->state = $state; } /** * @return mixed */ public function getState() { return $this->state; } //创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象 public function CreateMemento() { return new Memento($this->state); } //恢复备忘录 将Memento导入并恢复相关数据 public function setMemento(Memento $memento) { $this->state = $memento->getState(); } //显示数据 public function show() { echo 'State:'.$this->state."<br>"; } }Memento.php<?php namespace DesignPatterns\Memento\example; /** * 备忘录类 * Class Memento * @package DesignPatterns\Memento\example */ class Memento { private $state; //构造方法 将相关数据导入 public function __construct($state) { $this->state = $state; } //需要保存的数据可能是多个 /** * @param mixed $state */ public function setState($state): void { $this->state = $state; } /** * @return mixed */ public function getState() { return $this->state; } }Caretaker.php<?php namespace DesignPatterns\Memento\example; /** * 管理者类 * Class Caretaker * @package DesignPatterns\Memento\example */ class Caretaker { private $memento; //获取或者设置备忘录 /** * @return mixed */ public function getMemento() { return $this->memento; } /** * @param mixed $memento */ public function setMemento($memento): void { $this->memento = $memento; } }Client.php<?php namespace DesignPatterns\Memento\example; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $originator = new Originator(); $originator->setState('on');//Originator 初始状态为on $originator->show(); //保存状态时,由于有了很好的封装,可以隐藏Originator的实现细节 $caretaker = new Caretaker(); $caretaker->setMemento($originator->CreateMemento()); //更改状态属性为off $originator->setState('off'); $originator->show(); $originator->setMemento($caretaker->getMemento()); $originator->show(); } } $worker = new Client(); $worker->run();运行结果代码中把要保存的细节给封装在了Memento中了,如果以后哪天要更改保存的细节也不会影响客户端了。Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的Memento信息还原到前一状态。使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。下面我们来使用备忘录模式把我们刚才的代码更改一下,代码如下:GameRole.php<?php namespace DesignPatterns\Memento\v2; /** * 游戏角色类 * Class GameRole * @package DesignPatterns\Memento\v2 */ class GameRole { private $vit;//生命力 private $atk;//攻击力 private $def;//防御力 /** * @return mixed */ public function getVit() { return $this->vit; } /** * @param mixed $vit */ public function setVit($vit): void { $this->vit = $vit; } /** * @return mixed */ public function getAtk() { return $this->atk; } /** * @param mixed $atk */ public function setAtk($atk): void { $this->atk = $atk; } /** * @return mixed */ public function getDef() { return $this->def; } /** * @param mixed $def */ public function setDef($def): void { $this->def = $def; } //获得初始状态 public function GetInitState(){ $this->vit = 100; $this->atk = 100; $this->def = 100; } //战斗 public function Fight(){ $this->vit = 0; $this->atk = 0; $this->def = 0; } //角色状态显示 public function StateDisplay() { echo "角色当前的状态:<br>体力:".$this->vit, "<br>攻击力:".$this->atk,"<br>防御力:".$this->def."<br>"; } //新增“保存角色状态”方法,将游戏角色的三个状态值通过实例化“角色状态存储箱”返回 //保存角色状态 public function SaveRoleState() { return new RoleStateMemento($this->vit,$this->atk,$this->def); } //恢复角色状态 public function RecoveryState(RoleStateMemento $memento) { $this->vit = $memento->getVit(); $this->atk = $memento->getAtk(); $this->def = $memento->getDef(); } }RoleStateMemento.php<?php namespace DesignPatterns\Memento\v2; /** * 角色状态储存箱 * Class RoleStateMemento * @package DesignPatterns\Memento\v2 */ class RoleStateMemento { private $vit;//生命力 private $atk;//攻击力 private $def;//防御力 public function __construct($vit,$atk,$def) { $this->vit = $vit; $this->atk = $atk; $this->def = $def; } /** * @param mixed $vit */ public function setVit($vit): void { $this->vit = $vit; } /** * @return mixed */ public function getVit() { return $this->vit; } /** * @param mixed $atk */ public function setAtk($atk): void { $this->atk = $atk; } /** * @return mixed */ public function getAtk() { return $this->atk; } /** * @param mixed $def */ public function setDef($def): void { $this->def = $def; } /** * @return mixed */ public function getDef() { return $this->def; } }RoleStateCaretaker.php<?php namespace DesignPatterns\Memento\v2; /** * 角色状态管理者类 * Class RoleStateCaretaker * @package DesignPatterns\Memento\v2 */ class RoleStateCaretaker { private $memento; /** * @param mixed $memento */ public function setMemento($memento): void { $this->memento = $memento; } /** * @return mixed */ public function getMemento() { return $this->memento; } }Client.php<?php namespace DesignPatterns\Memento\v2; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $gameRole = new GameRole(); //游戏角色初始状态,三项数据都是100 $gameRole->GetInitState(); $gameRole->StateDisplay(); //保存进度 //保存进度时,由于封装在Memento中,因此我们并不知道保存了哪些具体的角色数据 $RoleStateCaretaker = new RoleStateCaretaker(); $RoleStateCaretaker->setMemento($gameRole->SaveRoleState()); //大战boss,损失严重 $gameRole->Fight(); $gameRole->StateDisplay(); //恢复之前的状态 $gameRole->RecoveryState($RoleStateCaretaker->getMemento()); $gameRole->StateDisplay(); } } $worker = new Client(); $worker->run();运行结果:总结备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。备忘录模式将代码中把要保存的细节给封装在了Memento中了,如果以后哪天要更改保存的细节也不会影响客户端了。备忘录模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator 可以根据保存的Memento信息还原到前一状态。使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来。github示例:https://github.com/yangpanyao/design-patterns/tree/master/Memento
2020年07月17日
1,234 阅读
0 评论
0 点赞
2020-07-15
PHP设计模式-SPL迭代器
上篇文章中我们讲了迭代器模式的原理、实现和设计意图。迭代器模式主要作用是解耦容器代码和遍历代码,这也印证了我们前面多次讲过的应用设计模式的主要目的是解耦。其实在php中,PHP在php5版本后就已经在spl库内置了Iterator接口,以及大量的已经实现的迭代器,如数组,文件等遍历,今天我们就来讲解一些php中的迭代器。在讲解php的迭代器前我们先来了解一下PHP的spl。spl简介SPL (标准PHP库)是PHP 5面向对象功能中最重要的部分。它在5个关键的方面提升了PHP语言,包括:迭代器、异常、数组重载、XML以及文件和数据处理能力。它还提供了另外一些有用的项,例如观察者模式、计数功能、用于对象标识符的辅助函数以及迭代器处理功能。此外,它还提供了用于自动加载类和接口的高级功能。spl迭代器接口与迭代器SPL迭代器接口的作用在于帮助实现高级的迭代算法,允许为类创建精巧的数据访问方法。这些接口形成了创建迭代器类的基础。可以直接实现这些接口去创建所需的迭代器,不过,SPL扩展定义了更多的内置迭代器类,以完成最普通的迭代任务。我们先了解这些接口,然后再探讨其中一些内置的类。迭代器接口SPL提供了5个迭代器接口: Traversable、 Iterator、 IteratorAggregate、 OuterIteratorf 和 RecursiveIterator1.TraversableTraversable接口实际上不是一个接口,更像是一个特性。这是因为只有内部的类( 也就是用C语言编写的类)才可以直接实现Traversable接口。任何需要实现Traversabl e接口的用户自定义类都必须通过实现从Traversab1e接口派生的用户自定义接口来做到这一点。有两个派生自Traversable接口的基础级别的类,它们才是你的对象可以访问的接口,即Iterator 接口和 IteratorAggregate 接口。通过实现这两个接口之一,对象就可以和foreach语句一起使用了。2.IteratorIterator接口是在c代码内部定义的,可在内部迭代自己的外部迭代器或类的接口。Iterator接口定义如下:interface Iterator extends Traversable { /* 方法 */ abstract public current ( void ) : mixed abstract public key ( void ) : scalar abstract public next ( void ) : void abstract public rewind ( void ) : void abstract public valid ( void ) : bool }Iterator接口给我们提供了5个方法它们的作用具体如下:方法说明current()返回当前元素的值key()返回当前的键名称或索引值next()将数组指针向前一位指向下一个元素rewind()将数组指针移动到数组的开头位置valid()确定当前元素是否存在,并且必须在调用了next ()方法和rewind()方法之后才能调用这个方法3.IteratorAggregateIteratorAggregate接口是用来将Iterator接口要求实现的5个迭代器方法委托给其他类的。这让你可以在类的外部实现迭代功能,并允许重新使用常用的迭代器方法,而不是在编写的每个可迭代类中重复使用这些方法。IteratorAggregate接口定义如下:interface IteratorAggregate extends Traversable { public function getIterator(); }实现getIterator()方法时,必须返回一个实现了Iterator接口的类的实例。通常在getIterator ()方法内部,你会把类的信息传递给一个特殊的迭代器类的构造函数。这个数据可能是一个基础数组或者任何能够想到的其他数据,只要它足够控制5个Iterator方法即可。SPL提供了一些专门用来 与IteratorAggregate接口一起使用的内置迭代器。使用这些迭代器意味着只需要实现一个方法并实例化一个类就可以使对象可迭代访问了。如下代码就是ArrayIterator接口和IteratorAggregate接口一起使用的效果。 class MyIterableClass implements \IteratorAggregate { public $property1 = "Public property one"; public $property2 = "Public property two"; public $property3 = "Public property three"; public function __construct() { $this->property4 = "last property"; } public function getIterator() { return new \ArrayIterator($this); } } $obj = new MyIterableClass; foreach($obj as $key => $value) { echo($key." --- ".$value); echo "<br>"; }运行结果:4. OuterIterator 有时,将一个或多个迭代器包裹在另外一个迭代器中是很有用的,例如,在你希望按顺序迭代访问几个不同的迭代器时对于这一用途而言,可以使用outerIterator接口。outerIterator接口定义如下:interface OuterIterator extends Iterator { public function getInnerIterator(); }这个接口与IteratorAggregate接口的区别在于它扩展了Iterator接口,因此,所有实现它的类都必须实现Iterator接口定义的所有方法。getInnerIterator ()方法应该返回当前正在迭代访问的迭代器。例如,当两个或者更多的迭代器被添加在-起并且一个接一个地迭代访问时,随着数组指针通过next ()方法不断增加,getInnerIterator ()方法必须返回第一个迭代器,然后是第二个,以此类推。这一接口形成了其他几个更加专用的迭代器的基础接口,其中包括AppendIterator.CachingIterator、FilterIterator、 IteratorIterator、LimitIterator和RecursiveIteratorIterator接口。5. RecursiveIteratorRecursiverIterator接口的作用在于提供了递归迭代访问功能。这种类型的迭代器接口可以表达一个树形的数据结构,其中包含了节点元素和叶子元素或者父元素和子元素。目录就是一个递归结构的例子。RecursiveIterator接口定义如下:interface RecursiveIterator extends Iterator { public function hasChildren(); public function getChildren(); }所有的递归函数(调用自身的函数)都必须具有这样的功能,即决定是要继续递归操作,还是停止递归并返回到调用栈的顶部。hasChildren()方法提供了实现这一判断条件的功能。如果迭代器拥有子元素, getChildren()方法将会被调用,并且它应该返回子元素的一个迭代器实例。迭代器SPL提供了几种迭代器,分别提供了迭代访问迭代器、过滤数据、缓存结果、控制分页等功能。1.ArrayIteratorArrayIterator迭代器可以说是SPL包中最强大的迭代器之一。它允许从php数组中创建迭代器这个迭代器。如下:$arr = array('a','b','c','d'); $arrayIterator = new \ArrayIterator($arr); foreach ($arrayIterator as $value){ echo $value."\n"; } //输出结果 a b c d从这段代码并不能看出这个迭代器有多大作用,因为完全可以跳过创建迭代器的操作去实现同样的功能。不过,重要的是要理解如何从一个数组手工获得一个 迭代器,这是因为许多其他SPL迭代器的构造函数都需要传入一个迭代器,而不是数组。当ArrayIterator迭代器和IteratorAggregate类一起使用时, 免去了直接实现Iterator接口的方法的工作。如果类使用-一个数组作为底层的表示方式,只需在getIterator ()方法中返回一个ArrayIterator接口即可。2.LimitIteratorLimitIterator是一个比较简单的迭代器。它让你可以实现与SQL的LIMIT子句和OFFSET子句相同的迭代访问功能,它会返回给定数量的结果以及从集合中取出结果的起始索引点。LimitIterator迭代器实现了OuterIterator接口。LimitIterator的构造函数接受3个参数:迭代器、偏移量和限制数量。用法如下:$arr = array(1,2,3,4,5,6,7,8,9); $arrayIterator = new \ArrayIterator($arr); $limitIterator = new \LimitIterator($arrayIterator,3,4); foreach ($limitIterator as $value){ echo $value."\n"; } //输出结果 4 5 6 73.AppendIteratorAppendIterator迭代器允许按顺序迭代访问几个不同的迭代器。例如,如果希望在一个循环中迭代访问两个或更多的数组,需要使用AppendIterator迭代器。用法如下:$arrFirst = new \ArrayIterator(array(1,2,3)); $arrSecond = new \ArrayIterator(array(4,5,6)); $appendIterator = new \AppendIterator(); $appendIterator->append($arrFirst); $appendIterator->append($arrSecond); foreach ($appendIterator as $value) { echo $value."\n"; } //输出结果 1 2 3 4 5 6可以看到,添加迭代器的顺序正是迭代访问迭代器的顺序。由于这里使用AppendIterator迭代器,迭代访问所有数组只需要一个循环就可以了。4.FilterIteratorFilterIterator类是一个基于outerIterator接口的迭代器。在迭代器中它被用来过滤数据,并且返回任何符合条件的元素。例如,FilterIterator类 可以和DirectoryIterator类一起使用,用来返回一个大文件列表。这个类有一个 必须实现的抽象方法accept ()。正因为这点,FilterIterator类只能作为基类使用。accept ()方法必须为迭代器中的当前项返回true或者false。如果返回的结果是true,那么那条记录将被包含在迭代过程中。如果结果是false,它将被排除在外。我们可以使用FilterIterator迭代器实现一个过滤功能,代码如下:class GreaterThanThreeFilterIterator extends \FilterIterator { public function accept() { return ($this->current() > 3); } } $arr = new \ArrayIterator(array(1,2,3,4,5,6)); $iterator = new GreaterThanThreeFilterIterator($arr); foreach ($iterator as $value) { echo $value."\n"; } //输出结果 4 5 65.RegexIteratorRegexIterator是从FilterIterator类继承来的,它允许使用几种正则表达式模式来匹配和修改迭代器的数据集。RegexIterator类最基本的用途是将字符串键值和数值与一个模式相匹配,并返回匹配的那些键值。下面是一个显示如何查找以字母a开头的所有项的代码示例。$arr = array('apple','avocado','orange','pineapple'); $arrayIterator = new \ArrayIterator($arr); $regexIterator = new \RegexIterator($arrayIterator,'/^a/'); print_r(iterator_to_array($regexIterator));//iterator_to_array — 将迭代器中的元素拷贝到数组 //输出结果 Array ( [0] => apple [1] => avocado )与上面的简单匹配代码相比,RegexIterator类还提供了更多的功能,他有几个其他的参数,可以用来修改他的工作方式。RegexIterator的构造函数如下:__construct ( Iterator $iterator , string $regex [, int $mode = self::MATCH [, int $flags = 0 [, int $preg_flags = 0 ]]] ) 第一个值得注意的参数是$mode,它控制了正则表达式的操作模式。这个参数默认为RegexIterator:MATCH,但也可以是以下值之一。GET_MATCH在这一模式中,迭代器的当前键值被传递给preg_match()函数的第三个参数。这会将当前键值替换为 &$matches数据。ALL_MATCHES.这一选项和GET_MATCH是相同的,但它使用的是preg_match_all函数而不是preg_ _match函数。SPLIT 这一模式使用preg_split函数,其含义与GET_MATCH和ALL_MATCHES是相同的。REPLACE 这一选项接受当前的迭代器值,并且会执行正则表达式替换操作,然后使用替换后的字符串覆盖当前的值。下面是将GET_MATCH作为$mode参数的用法$arr = array('apple','avocado','orange','pineapple'); $arrayIterator = new \ArrayIterator($arr); $regexIterator = new \RegexIterator($arrayIterator,'/^(a\w{3})\w*$/',RegexIterator::GET_MATCH); print_r(iterator_to_array($regexIterator)); 输出结果上面的代码用到的正则表达式非常简单。它配置整个基于单词的字符串,这个字符串以字母a开头并且包含三个或更多的字符。括号内的模式(a/w{3})控制了字符串的哪一部分将被GET_MATCH模式所使用。在这个例子中,GET_MATCH模式使用的部分包括字母a和三个字符。最终的数组包含了位置0上整个匹配的字符串,接着是任何捕捉到的子模式。下一个RegexIterator类的参数$flags允许通知迭代器要它处理数组的键值还是数组的值。默认情况下是使用数组的值,不过,可以给这个参数传入类的常量USE_KEY以启用键值操作。下面的代码显示如何使用RegexIterator类过滤一个 数组,使得数组中只包含键值为数字的项。最终的数组将只包含数字键值以及与数字键值相关联的值。$arr = array('0'=>'A','1'=>'B','2'=>'C','3'=>'D','nonnumeric'=>'useless'); $arrayIterator = new \ArrayIterator($arr); $regexIterator = new \RegexIterator( $arrayIterator, '/^\d*$/', RegexIterator::MATCH, RegexIterator::USE_KEY); print_r(iterator_to_array($regexIterator)); //输出结果 Array ( [0] => A [1] => B [2] => C [3] => D )RegexIterator类的最后一个参数$preg_flags是用来向内部的preg函数传入所需的preg参数的。6.IteratorIterator一开始理解IteratorIterator迭代器是比较困难的,但实际上它是SPL提供的最酷的迭代器之一。IteratorIterator是一种通用类型的迭代器,这意味着所有实现了Traversable接口的类都可以被它迭代访问。起初,这看起来并不是很有用,但是在多种PHP的扩展中都会有一些类没有实现Iterator接口或者IteratorAggregate接口,但却实现了Traversable接口。其中的一个例子是PDO (PHP数据对象)扩展。在PDO中,PDOStatement类 只实现了Traversable接口,这是因为它是一个只读集合,也正因为如此,实现高层次的迭代器接口是不合适的。这一只读限制的结果是,要将PDOStatement类的结果和本章描述过的任何其他迭代器相结合,需要使用IteratorIterator迭代器去封装PDOStatement类。由于PDOStatement类的只读特性,使用像RegexIterator这样的可修改的迭代器可能会带来问题,所以不应该在PDOStatement类上使用这些迭代器。也就是说,像AppendIterator、FilterIterator和LimitIterator这样功能强大的迭代器在与pDoStatement结果集组合在一起使用时,将会非常有用。下面的代码显示了如何使用IteratorIterator迭代器和LimitIterator迭代器来限制结果集。$db = new \PDO( 'pgsql:dbname=yourdb; user=youruser') ; $pdoStatement = $db->query( ' SELECT * FROM table') ; $iterator = new \IteratorIterator ($pdoStatement) ; $limitIterator = new \LimitIterator ($iterator , 0, 10) ; $tenRecordArray = iterator_to_array($limitIterator) ;说明:上面代码只作为演示用途。如果希望限制结果集只包含十条记录,那么应该在SQL查询语句中使用SQL LIMIT语法。7.CachingIteratorCachingIterator迭代器用来执行提前读取一个元素的迭代操作。在迭代循环的某一特定点上,当需要了解关于下一个元素的信息时,这些迭代器会非常有用。例如,可以使用这一迭代器来确定当前元素是否为列表中的最后一个元素。使用递归形式,可以在一个树形结构中通过查找关于下一个键值的信息来确定当前节点是节点还是项。$iterator = new \CachingIterator(new ArrayIterator(['C', 'C++', 'C#', 'PHP', 'Python', 'Go', 'Ruby'])); foreach ($iterator as $item) { if ($iterator->hasNext()) { echo $item.', '; } else { echo 'and '.$item; } } //输出结果 : C, C++, C#, PHP, Python, Go, and Ruby8.SeekableIteratorSeekableIterator迭代器可用于创建非顺序访问的迭代器。它允许跳转到迭代器中的任何一点上,而不用迭代访问所有这一点之前的元素。$array = array('a','b','c','d'); //ArrayIterator 实现了接口 SeekableIterator $iterator = new ArrayIterator($array); $iterator->seek(3); echo $iterator->current(); //输出结果 d9.NoRewindIteratorNoRewindIterator迭代器可用于不能回卷的集合,或者说是不能多次迭代的集合。当你需要在迭代过程中执行一次性操作时,这种迭代器非常有用。例如,在迭代过程中插入数据库记录时。$iterator = new \ArrayIterator(['PHP', 'Python', 'Go']); $iterator = new \NoRewindIterator($iterator); foreach ($iterator as $item) { echo $item.PHP_EOL; } // doesn't do anything foreach ($iterator as $item) { echo $item.PHP_EOL; } //输出结果 : PHP Python Go10.EmptyIteratorEmptyIterator是一种占位符形式的迭代器,它不执行任何操作。当要实现某个抽象类的方法并且这个方法需要返回一个迭代器时,可以使用这一迭代器。它也可以用于执行迭代器之间的比较操作11.InfiniteIteratorInfiniteIterator迭代器用来持续地循环访问数据。当迭代操作找到最后一个元素时,迭代器会回卷,并再次从第一个元素开始迭代访问数据。$obj = new stdClass(); $obj->Mon = "Monday"; $obj->Tue = "Tuesday"; $obj->Wed = "Wednesday"; $obj->Thu = "Thursday"; $obj->Fri = "Friday"; $obj->Sat = "Saturday"; $obj->Sun = "Sunday"; $infiniteIterator = new InfiniteIterator(new ArrayIterator($obj)); foreach ( new LimitIterator($infiniteIterator, 0, 14) as $value ) { print($value . PHP_EOL); } /* 输出结果 Monday Tuesday Wednesday Thursday Friday Saturday Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday */12.RecursiveArrayIteratorRecursiveArrayIterator迭代器允许创建一个用于递归形式数组结构(类似于多维数组)的迭代器。这是一个很重要的迭代器,因为它为许多更复杂的迭代器提供了所需的操作,如RecursiveTreeIterator迭代器和RecursiveArrayIterator迭代器下面是RecursiveArrayIterator 迭代器的用法:$arr = array ( 0=> 'a' , 1 => array('a','b', 'c') , 2 => 'b', 3 => array('a', 'b', 'c') , 4 => 'c' ); $it = new \RecursiveArrayIterator ($arr) ; while ($it->valid()) { //检查是否含有子节点 if($it->hasChildren()){ //输出所有子节点 foreach ($it->getChildren() as $key => $value) { echo $key.'---'.$value."<br>"; } }else{ echo"No children.<br>"; } $it->next(); } /** 运行结果 No children. 0---a 1---b 2---c No children. 0---a 1---b 2---c */13.RecursiveIteratorIteratorRecursiveIteratorIterator允许获得一个树形结构,并将它展开为一维结构,当这个迭代器发现一个子迭代器时,它会访问这个子迭代器。$arr = array ( 0=> 'a' , 1 => array('a','b', 'c') , 2 => 'b', 3 => array('a', 'b', 'c') , 4 => 'c' ); $arrayIterator = new \RecursiveArrayIterator ($arr) ; $it = new \RecursiveIteratorIterator($arrayIterator); print_r(iterator_to_array($it,false)); /* 运行结果: Array ( [0] => a [1] => a [2] => b [3] => c [4] => b [5] => a [6] => b [7] => c [8] => c ) */可以看到上一个代码运行的结果得到的就是一个一维数组RecursiveIteratorIterator迭代器的构造函数如下:public RecursiveIteratorIterator::__construct ( Traversable $iterator [, int $mode = RecursiveIteratorIterator::LEAVES_ONLY [, int $flags = 0 ]] ) $mode参数可以接受以下三个类常量之一: CHILD_FIRST、SELF_FIRST 或者默认的 JEAVES_ONLY这一模式参数控制节点是否包含在树中以及它们是以何种顺序包含在树中。例如,一个目录结构最好被打印成一个SELF_FIRST形 式的树,但是在搜索文件时,使用LEAVES_ONLY会更合理一些。$flags参数可以被设置为CATCH_GET_CHILDREN,可以在调用子迭代器时捕获所有的异常,并且在抛出异常时继续访问下一个元素。14.RecursiveTreeIteratorRecursiveTreeIterator 不是一个内置的迭代器,它是example目录中的/ext/spl/examples/recursivetreeiterator.inc文件中的SPL扩展提供的一个示例迭代器。使用这个迭代器,可以显示一个树形结构$arr = array ( 0=> 'a' , 1 => array('a','b', 'c') , 2 => 'b', 3 => array('a', 'b', 'c') , 4 => 'c' ); $arrayIterator = new \RecursiveArrayIterator ($arr) ; $it = new \RecursiveTreeIterator($arrayIterator); print_r(iterator_to_array($it,false)); /* 运行结果: Array ( [0] => |-a [1] => |-Array [2] => | |-a [3] => | |-b [4] => | \-c [5] => |-b [6] => |-Array [7] => | |-a [8] => | |-b [9] => | \-c [10] => \-c ) */RecursiveTreeIterator迭代器和所有内含的示例迭代器一样都是用于扩展的。并没有要求树形结构迭代器必须输出基于文本的分界符。构造一个输出HTML形式的树形结构的迭代器也是可以的。15.ParentIteratorParentIterator是一个扩展的FilterIterator迭代器,(它可以过滤掉来自于RecursiveIterator迭代器的非父元素,从而只找出拥有子元素的键值)$arr = array ( 0=> 'a' , 1 => array('a','b', 'c') , 2 => 'b', 3 => array('a', 'b', 'c') , 4 => 'c' ); $arrayIterator = new \RecursiveArrayIterator ($arr) ; $it = new \ParentIterator($arrayIterator); print_r(iterator_to_array($it,false)); /* 运行结果: Array ( [0] => Array ( [0] => a [1] => b [2] => c ) [1] => Array ( [0] => a [1] => b [2] => c ) ) */16.RecursiveFilterIteratorRecursiveFilterIterator迭代器是FilterIterator迭代器的递归形式。与之前的迭代器一样,它要求实现抽象的accept()方法,但是在这个方法中,应该使用$this->getInnerIterator()方法访问当前正在迭代的迭代器。17.RecursiveRegexIteratorRecursiveRegexIterator迭代器是RegexIterator迭代器的递归形式。它和RegexIterator迭代器的用法一样,不同的是它只能接受RecursiveIterator迭代器作为迭代的对象。18.RecursiveCachingIteratorRecursiveCachingIterator迭代器在RecursiveIterator迭代器上执行提前读取一个元素的递归操作。在确定当前迭代的项是叶子还是节点时,它非常有用。19.CallbackFilterIterator 同时执行过滤和回调操作,在找到一个匹配的元素之后会调用回调函数 $arr = array(1,2,3,4,5,6); $arrayIterator = new \RecursiveArrayIterator($arr); function GreaterThanThree($current) { return $current > 3 != false; } $rs = new \CallbackFilterIterator($arrayIterator, 'GreaterThanThree'); print_r(iterator_to_array($rs)); /* 输出结果 Array ( [3] => 4 [4] => 5 [5] => 6 ) */20.DirectoryIteratorDirectoryIterator类提供了一个简单的接口,用于查看文件系统目录的内容方法描述DirectoryIterator::getSize得到文件大小DirectoryIterator::getType得到文件类型DirectoryIterator::isDir如果当前项是一个目录,返回trueDirectoryIterator::isDot如果当前项是.或..,返回trueDirectoryIterator::isExecutable如果文件可执行,返回trueDirectoryIterator::isFile如果文件是一个常规文件,返回trueDirectoryIterator::isLink如果文件是一个符号链接,返回trueDirectoryIterator::isReadable如果文件可读,返回trueDirectoryIterator::isWritable如果文件可写,返回trueDirectoryIterator::key返回当前目录项DirectoryIterator::next移动到下一项DirectoryIterator::rewind将目录指针返回到开始位置DirectoryIterator::valid检查目录中是否包含更多项foreach (new DirectoryIterator('../') as $fileInfo) { if($fileInfo->isDot()) continue; echo $fileInfo->getFilename() . "<br>\n"; }21.RecursiveDirectoryIteratorRecursiveDirectoryIterator提供了一个接口,用于在文件系统目录上递归迭代。22.FilesystemIterator FilesystemIterator是继承于DirectoryIterator的遍历器23.GlobIterator GlobIterator是一个带匹配模式的文件遍历器$iterator = new \GlobIterator('*.php'); if (!$iterator->count()) { echo'无php文件'; } else { $n = 0; printf("总计 %d 个php文件\r\n", $iterator->count()); foreach ($iterator as $item) { printf("[%d] %s\r\n", ++$n, $iterator->key()); } } /* 输出结果 总计 18 个php文件 [1] AppendIterator.php [2] ArrayIterator.php [3] CachingIterator.php [4] CallbackFilterIterator.php [5] DirectoryIterator.php [6] GlobIterator.php [7] GreaterThanThreeFilterIterator.php [8] InfiniteIterator.php [9] IteratorIterator.php [10] LimitIterator.php [11] MyIterableClass.php [12] NoRewindIterator.php [13] ParentIterator.php [14] RecursiveArrayIterator.php [15] RecursiveIteratorIterator.php [16] RecursiveTreeIterator.php [17] RegexIterator.php [18] SeekableIterator.php */24. MultipleIterator对所有附加迭代器进行顺序迭代的迭代器$person_id = new \ArrayIterator(array('001', '002', '003')); $person_name = new \ArrayIterator(array('张三', '李四', '王五')); $person_age = new \ArrayIterator(array(22, 23, 11)); $mit = new \MultipleIterator(MultipleIterator::MIT_KEYS_ASSOC); $mit->attachIterator($person_id, "ID"); $mit->attachIterator($person_name, "NAME"); $mit->attachIterator($person_age, "AGE"); echo"连接的迭代器个数:".$mit->countIterators() . "\n"; foreach ($mit as $person) { print_r($person); } /* 输出结果 连接的迭代器个数:3 Array ( [ID] => 001 [NAME] => 张三 [AGE] => 22 ) Array ( [ID] => 002 [NAME] => 李四 [AGE] => 23 ) Array ( [ID] => 003 [NAME] => 王五 [AGE] => 11 ) */25.RecursiveCallbackFilterIterator在RecursiveIterator迭代器上进行递归操作,同时执行过滤和回调操作,在找到一个匹配的元素之后会调用回调函数。总结本篇文章讲解了SPL的迭代器,它可以为复杂的编程问题提供优良的解决方法。为了使用foreach控制循环访问对象,你的类必须实现Traversable接口,但不能直接实现Traversable接口,而必须通过Iterator接口或者IteratorAggregate接口实现它。Iterator接口要求实现所有的迭代器方法。IteratorAggregate接 口允许将这些方法的实现放到其他类中。作为示例,你看到了如何使用内置的ArrayIterator迭代器迭代访问基于数组的集合,而且只需要实现IteratorAggregate接口中要求的getIterator()方法即可。SPL还提供了更为复杂的迭代器接口,其中包括RecursiveIterator、OuterIterator和seekableIterator。SPL提供了一系列实现了迭代器接口的迭代器类,可用于解决常见的编程难题。值得注意的是ArrayIterator、LimitIterator、FilterIterator和RegexIterator等迭代器以及它们的递归形式。代码示例:https://github.com/yangpanyao/design-patterns/tree/master/Iterator/example2
2020年07月15日
1,220 阅读
0 评论
0 点赞
2020-07-14
PHP设计模式-迭代器模式
上一篇文章中,我们学习了状态模式。状态模式是状态机的一种实现方法。它通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,以此来避免状态机类中的分支判断逻辑,应对状态机类代码的复杂性。今天,我们学习另外一种行为型设计模式,迭代器模式。它用来遍历集合对象。不过,很多编程语言都将迭代器作为一个基础的类库,直接提供出来了。在平时开发中,特别是业务开发,我们直接使用即可,很少会自己去实现一个迭代器。不过,知其然知其所以然,弄懂原理能帮助我们更好的使用这些工具类,所以,我觉得还是有必要学习一下这个模式。我们知道,大部分编程语言都提供了多种遍历集合的方式,比如 for 循环、foreach 循环、迭代器等。所以,今天我们除了讲解迭代器的原理和实现之外,还会重点讲一下,相对于其他遍历方式,利用迭代器来遍历集合的优势。迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。在开篇中我们讲到,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。迭代器的结构与实现结构迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口,这个具体迭代器角色于该容器的结构相关。接下来,我们通过一个例子来具体讲,如何实现一个迭代器。开篇中我们有提到,大部分编程语言都提供了遍历容器的迭代器类,PHP亦是如此。PHP 拥有内置的迭代器接口, 可用于创建与其他 PHP 代码兼容的自定义迭代器。我们在平时开发中,直接拿来用即可,几乎不大可能从零编写一个迭代器。不过,这里为了讲解迭代器的实现原理,我们假设某个新的编程语言的基础类库中,还没有提供线性容器对应的迭代器,需要我们从零开始开发。现在,我们一块来看具体该如何去做。接下来我们以数组为例来写一个迭代器对数组集合进行处理。我们先来看下 Iterator 接口的定义。具体的代码如下所示:interface Iterator { public function first(); public function next(); public function isDone(); public function currentItem(); }在代码中,first操作初始化迭代器,使当前元素指向第一个元素,next操作将当前元素指针向前推进一步,isDone检查是否越过最后一个元素,也就是是否完成遍历。currentItem()方法用于返回当前元素。现在,我们再来看下 ArrayIterator 的代码实现,具体如下所示。 class ArrayIterator implements Iterator { private $current; private $list =[]; public function __construct(array $list) { $this->list = $list; $this->current = 0; } public function first() { $this->current[0]; } public function next() { $this->current++; if ($this->current < count($this->list)) { return $this->list[$this->current]; } } public function isDone() { return $this->current >= count($this->list); } public function currentItem() { return $this->list[$this->current]; } } $arrayIterator = new ArrayIterator(array(1,2,3,4)); while (!$arrayIterator->isDone()) { echo $arrayIterator->currentItem()."\n"; $arrayIterator->next(); } //运行结果 : 1 2 3 4在上面的代码实现中,我们需要将待遍历的容器对象,通过构造函数传递给迭代器类。实际上,为了封装迭代器的创建细节,我们可以在容器中定义一个 iterator() 方法,来创建对应的迭代器。为了能实现基于接口而非实现编程,我们还需要将这个方法定义在Container接口中。具体的代码实现和使用示例如下所示:interface Container { public function iterator(array $list); } class ArrayContainer implements Container { public function iterator(array $list) { return new ArrayIterator($list); } } //clientCode $arrayContainer = new ArrayContainer(); $arrayIterator = $arrayContainer->iterator(array(1,2,3,4)); while (!$arrayIterator->isDone()) { echo $arrayIterator->currentItem()."\n"; $arrayIterator->next(); } 结合刚刚的例子,我们来总结一下迭代器的设计思路。其实通过客户端的调用,我们发现迭代器中需要定义 的其实只要有isDone()、currentItem()、next() 三个最基本的方法。待遍历的容器或数据通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。迭代器模式的优势迭代器的原理和代码实现讲完了。接下来,我们来一块看一下,使用迭代器遍历集合的优势。一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。对于这三种方式,我们分别用代码写出来进行比较//第一种遍历方式:for循环 for ($i=0;$i<count($arr);$i++){ echo $arr[$i]."\n"; } echo '<br>'; //第二种遍历方式:foreach循环 foreach ($arr as $value){ echo $value."\n"; } echo '<br>'; //第三种遍历方式:迭代器遍历 $arrayContainer = new ArrayContainer(); $arrayIterator = $arrayContainer->iterator($arr); while (!$arrayIterator->isDone()) { echo $arrayIterator->currentItem()."\n"; $arrayIterator->next(); }实际上,foreach 循环只是一个语法糖而已,底层是基于迭代器来实现的。也就是说,上面代码中的第二种遍历方式(foreach 循环代码)的底层实现,就是第三种遍历方式(迭代器遍历代码)。这两种遍历方式可以看作同一种遍历方式,也就是迭代器遍历方式。从上面的代码来看,for 循环遍历方式比起迭代器遍历方式,代码看起来更加简洁。那我们为什么还要用迭代器来遍历容器呢?为什么还要给容器设计对应的迭代器呢?原因有以下三个。首先,对于类似数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。前面也多次提到,应对复杂性的方法就是拆分。我们可以将遍历操作拆分到迭代器类中。比如,针对图的遍历,我们就可以定义 DFSIterator、BFSIterator 两个迭代器类,让它们分别来实现深度优先遍历和广度优先遍历。其次,将游标指向的当前位置等信息,存储在迭代器类中,每个迭代器独享游标信息。这样,我们就可以创建多个不同的迭代器,同时对同一个容器进行遍历而互不影响。最后,容器和迭代器都提供了抽象的接口,方便我们在开发的时候,基于接口而非具体的实现编程。当需要切换新的遍历算法的时候,比如,从前往后遍历链表切换成从后往前遍历链表,客户端代码只需要将迭代器类从 LinkedIterator 切换为 ReversedLinkedIterator 即可,其他代码都不需要修改。除此之外,添加新的遍历算法,我们只需要扩展新的迭代器类,也更符合开闭原则。总结迭代器模式,也叫游标模式。它用来遍历集合对象。这里说的“集合对象”,我们也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。一个完整的迭代器模式,一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。容器中需要定义 iterator() 方法,用来创建迭代器。迭代器接口中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。容器对象通过依赖注入传递到迭代器类中。遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有下面三个优势:迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。参考资料:设计模式之美-迭代器模式(上):相比直接遍历集合数据,使用迭代器有哪些优势github示例 :https://github.com/yangpanyao/design-patterns/tree/master/Iterator/example1
2020年07月14日
1,122 阅读
0 评论
0 点赞
2020-07-09
PHP设计模式-状态模式
在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点像我们之前讲到的组合模式。状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,还有分支逻辑法与查表法。今天,我们就详细讲讲这几种实现方式,并且对比一下它们的优劣和应用场景。什么是有限状态机?有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。对于刚刚给出的状态机的定义,我结合一个具体的例子,来进一步解释一下“超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。为了方便接下来的讲解,我们对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:状态机实现方式一:分支逻辑法我们如何编程来实现上面的状态机呢?最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的 if-else 或 switch-case 分支判断逻辑,甚至是嵌套的分支判断逻辑,所以,我们把这种方法暂且命名为分支逻辑法。如下所示。其中,obtainMushRoom()、obtainCape()、obtainFireFlower()、meetMonster() 这几个函数,能够根据当前的状态和事件,更新状态和增减积分。<?php namespace DesignPatterns\State\example2\v1; class MarioStateMachine { const Mario = array( 'SMALL'=>0,//小马里奥 'SUPER'=>1,//超级马里奥 'CAPE'=>2,//斗篷马里奥 'FIRE'=>3//火焰马里奥 ); private $score; private $currentState;//当前状态 public function __construct() { $this->score = 0; $this->currentState = self::Mario['SMALL']; } //获得蘑菇 public function obtainMushRoom() { if ($this->currentState == self::Mario['SMALL']){ $this->currentState = self::Mario['SUPER']; $this->score +=100; } } //获得斗篷 public function obtainCape() { if ($this->currentState == self::Mario['SMALL'] || $this->currentState == self::Mario['SUPER']) { $this->currentState = self::Mario['CAPE']; $this->score +=200; } } //获得火焰 public function obtainFireFlower() { if ($this->currentState == self::Mario['SMALL'] || $this->currentState == self::Mario['SUPER']) { $this->currentState = self::Mario['FIRE']; $this->score +=300; } } //遇到怪物 public function meetMonster(){ if ($this->currentState == self::Mario['SUPER']) { $this->currentState = self::Mario['SMALL']; $this->score -=100; return; } if ($this->currentState == self::Mario['CAPE']) { $this->currentState = self::Mario['SMALL']; $this->score -=200; return; } if ($this->currentState == self::Mario['FIRE']) { $this->currentState = self::Mario['SMALL']; $this->score -=300; return; } } /** * @return int */ public function getScore(): int { return $this->score; } /** * @return int */ public function getCurrentState() { return array_search($this->currentState,self::Mario); } } //clientCode $mario = new MarioStateMachine(); $mario->obtainMushRoom();//获取蘑菇 $mario->obtainCape();//获取斗篷 $mario->meetMonster();//遇到怪物 $score = $mario->getScore(); $state = $mario->getCurrentState(); echo "mario score: ".$score." state: ".$state; 对于简单的状态机来说,分支逻辑这种实现方式是可以接受的。但是,对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转移。除此之外,代码中充斥着大量的 if-else 或者 switch-case 分支判断逻辑,可读性和可维护性都很差。如果哪天修改了状态机中的某个状态转移,我们要在冗长的分支逻辑中找到对应的代码进行修改,很容易改错,引入 bug。状态机实现方式二:查表法实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,我们就一块儿来看下,如何利用查表法来实现状态机。实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transitionTable 和 actionTable 两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代码,只需要修改配置文件就可以了。具体的代码如下所示:<?php namespace DesignPatterns\State\example2\v2; class MarioStateMachine { const Mario = array( 'SMALL'=>0,//小马里奥 'SUPER'=>1,//超级马里奥 'CAPE'=>2,//斗篷马里奥 'FIRE'=>3//火焰马里奥 ); const GOT = array( 'GOT_MUSHROOM'=>0, 'GOT_CAPE'=>1, 'GOT_FIRE'=>2, 'MET_MONSTER'=>3 ); private $transitionTable = [ ['SUPER', 'CAPE', 'FIRE', 'SMALL'], ['SUPER', 'CAPE', 'FIRE', 'SMALL'], ['CAPE','CAPE','CAPE','SMALL'], ['FIRE', 'FIRE', 'FIRE', 'SMALL'],]; private $actionTable = [ [+100, +200, +300, +0], [+0, +200, +300, -100], [+0, +0, +0, -200], [+0, +0, +0, -300]]; private $score; private $currentState;//当前状态 private $stateValue; private $eventValue; public function __construct() { $this->score = 0; $this->currentState = self::Mario['SMALL']; } //获得蘑菇 public function obtainMushRoom() { $this->executeEvent( self::GOT['GOT_MUSHROOM']); } //获得斗篷 public function obtainCape() { $this->executeEvent( self::GOT['GOT_CAPE']); } //获得火焰 public function obtainFireFlower() { $this->executeEvent( self::GOT['GOT_FIRE']); } //遇到怪物 public function meetMonster(){ $this->executeEvent( self::GOT['MET_MONSTER']); } public function executeEvent($eventValue){ $stateValue = $this->currentState; $this->currentState = self::Mario[$this->transitionTable[$stateValue][$eventValue]]; $this->score += $this->actionTable[$stateValue][$eventValue]; } /** * @return int */ public function getScore(): int { return $this->score; } /** * @return int */ public function getCurrentState() { return array_search($this->currentState,self::Mario); } } //clientCode $mario = new MarioStateMachine(); $mario->obtainMushRoom();//获取蘑菇 $mario->obtainFireFlower();//获取斗篷 $mario->meetMonster();//遇到怪物 $score = $mario->getScore(); $state = $mario->getCurrentState(); echo "mario score: ".$score." state: ".$state;状态机实现方式三:状态模式在查表法的代码实现中,事件触发的动作只是简单的积分加减,所以,我们用一个 int 类型的二维数组 actionTable 就能表示,二维数组中的值表示积分的加减值。但是,如果要执行的动作并非这么简单,而是一系列复杂的逻辑操作(比如加减积分、写数据库,还有可能发送消息通知等等),我们就没法用如此简单的二维数组来表示了。这也就是说,查表法的实现方式有一定局限性。虽然分支逻辑的实现方式不存在这个问题,但它又存在前面讲到的其他问题,比如分支判断逻辑较多,导致代码可读性和可维护性不好等。实际上,针对分支逻辑法存在的问题,我们可以使用状态模式来解决。我们先来了解一下状态模式状态模式状态(State) 模式在GOF《设计模式》中给出定义为:允许一个对象在其内部状态改变时改变它的行为。这个对象看起来似乎修改了它的类。看起来,状态模式好像是神通广大——居然能够“修改自身的类”!能够让程序根据不同的外部情况来做出不同的响应,最直接的方法就是在程序中将这些可能发生的外部情况全部考虑到,使用 if else 语句来进行代码响应选择。但是这种方法对于复杂一点的状态判断,就会显得杂乱无章,容易产生错误;而且增加一个新的状态将会带来大量的修改。这个时候“能够修改自身”的状态模式的引入也许是个不错的主意。状态模式可以有效的替换充满在程序中的 if else 语句:将不同条件下的行为封装在一个类里面,再给这些类一个统一的父类来约束他们。我们来看一下状态模式的结构状态模式的结构与实现结构上下文(Context)角色:客户程序是通过它来满足自己的需求。它定义了客户程序需要的接口;并且维护一个具体状态角色的实例,这个实例来决定当前的状态。状态(State)角色:定义一个接口以封装与使用环境角色的一个特定状态相关的行为。具体状态(Concrete State)角色:实现状态角色定义的接口。代码实现State.php<?php namespace DesignPatterns\State\example1; /** * State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为。 * Class State * @package DesignPatterns\State\example1 */ abstract class State { abstract public function Handle(Context $context); }ConcreteStateA.php<?php namespace DesignPatterns\State\example1; /** * ConcreteState类,具体状态,每一个子类实现一个与Context的一个状态相关的行为。 * Class ConcreteStateA * @package DesignPatterns\State\example1 */ class ConcreteStateA extends State { public function Handle(Context $context) { echo "ConcreteStateA Handler<br>"; //设置ConcreteStateA的下一状态是ConcreteStateB $context->setState(new ConcreteStateB()) ; } }ConcreteStateB.php<?php namespace DesignPatterns\State\example1; /** * Class ConcreteStateB * @package DesignPatterns\State\example1 */ class ConcreteStateB extends State { public function Handle(Context $context) { echo "ConcreteStateB Handler<br>"; //设置ConcreteStateA的下一状态是ConcreteStateA $context->setState(new ConcreteStateA()); } }Context.php<?php namespace DesignPatterns\State\example1; /** * Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态。 * Class Context * @package DesignPatterns\State\example1 */ class Context { private $state; //定义context的初始状态 public function __construct(State $state) { $this->state = $state; } /** * @return State */ public function getState(): State { return $this->state; } /** * @param State $state */ public function setState(State $state): void { $this->state = $state; } //对请求做处理,并设置下一状态 public function Request() { $this->state->Handle($this); } }运行 Client.php<?php namespace DesignPatterns\State\example1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $context = new Context(new ConcreteStateA()); $context->request(); $context->request(); $context->request(); $context->request(); } } $worker = new Client(); $worker->run();运行结果:状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑。我们回到这个例子,利用状态模式,我们来补全 MarioStateMachine 类,补全后的代码如下所示。其中,IMario 是状态的接口,定义了所有的事件。SmallMario、SuperMario、CapeMario、FireMario 是 IMario 接口的实现类,分别对应状态机中的 4 个状态。原来所有的状态转移和动作执行的代码逻辑,都集中在 MarioStateMachine 类中,现在,这些代码逻辑被分散到了这 4 个状态类中。IMario.php<?php namespace DesignPatterns\State\example2\v3; /** * Interface IMario * @package DesignPatterns\State\example2\v3 */ interface IMario { public function getName(); //获取蘑菇 public function obtainMushRoom(); //获取斗篷 public function obtainCape(); //获得火焰 public function obtainFireFlower(); //遇到怪物 public function meetMonster(); }SmallMario.php<?php namespace DesignPatterns\State\example2\v3; /** * Class SmallMario * @package DesignPatterns\State\example2\v3 */ class SmallMario implements IMario { private $stateMachine; public function __construct(MarioStateMachine $stateMachine) { $this->stateMachine = $stateMachine; } public function getName() { return $this->stateMachine::Mario['SMALL']; } public function obtainMushRoom() { $this->stateMachine->setCurrentState(new SuperMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore()+100); } public function obtainCape() { $this->stateMachine->setCurrentState(new CapeMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore()+200); } public function obtainFireFlower() { $this->stateMachine->setCurrentState(new FireMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore()+300); } public function meetMonster() { // do nothing... } }SuperMario.php<?php namespace DesignPatterns\State\example2\v3; class SuperMario implements IMario { private $stateMachine; public function __construct(MarioStateMachine $stateMachine) { $this->stateMachine = $stateMachine; } public function getName() { return $this->stateMachine::Mario['SUPER']; } public function obtainMushRoom() { // do nothing... } public function obtainCape() { $this->stateMachine->setCurrentState(new CapeMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore() + 200); } public function obtainFireFlower() { $this->stateMachine->setCurrentState(new FireMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore() + 300); } public function meetMonster() { $this->stateMachine->setCurrentState(new SmallMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore() - 100); } }CapeMario.php<?php namespace DesignPatterns\State\example2\v3; class CapeMario implements IMario { private $stateMachine; public function __construct(MarioStateMachine $stateMachine) { $this->stateMachine = $stateMachine; } public function getName() { return $this->stateMachine::Mario['CAPE']; } public function obtainMushRoom() { //do nothing... } public function obtainCape() { //do nothing... } public function obtainFireFlower() { //do nothing... } public function meetMonster() { $this->stateMachine->setCurrentState(new SmallMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore()-200); } }FireMario.php<?php namespace DesignPatterns\State\example2\v3; class FireMario implements IMario { private $stateMachine; public function __construct(MarioStateMachine $stateMachine) { $this->stateMachine = $stateMachine; } public function getName() { return $this->stateMachine::Mario['FIRE']; } public function obtainMushRoom() { //do nothing... } public function obtainCape() { //do nothing... } public function obtainFireFlower() { //do nothing... } public function meetMonster() { $this->stateMachine->setCurrentState(new SmallMario($this->stateMachine)); $this->stateMachine->setScore($this->stateMachine->getScore()-300); } }MarioStateMachine.php<?php namespace DesignPatterns\State\example2\v3; class MarioStateMachine { private $score; private $currentState; const Mario = array( 'SMALL'=>0,//小马里奥 'SUPER'=>1,//超级马里奥 'CAPE'=>2,//斗篷马里奥 'FIRE'=>3//火焰马里奥 ); public function __construct() { $this->score = 0; $this->currentState = new SmallMario($this); } public function obtainMushRoom() { $this->currentState->obtainMushRoom(); } public function obtainCape() { $this->currentState->obtainCape(); } public function obtainFireFlower() { $this->currentState->obtainFireFlower(); } public function meetMonster() { $this->currentState->meetMonster(); } /** * @return int */ public function getScore(): int { return $this->score; } public function getCurrentState() { return array_search($this->currentState->getName(),self::Mario); } /** * @param int $score */ public function setScore(int $score): void { $this->score = $score; } /** * @param IMario $currentState */ public function setCurrentState(IMario $currentState): void { $this->currentState = $currentState; } }运行 Client.php<?php namespace DesignPatterns\State\example2\v3; require dirname(dirname(__DIR__)).'/../vendor/autoload.php'; class Client { public function run() { $mario = new MarioStateMachine(); $mario->obtainMushRoom();//获取蘑菇 $mario->obtainCape();//获取斗篷 $mario->meetMonster();//遇到怪物 $score = $mario->getScore(); $state = $mario->getCurrentState(); echo "mario score: ".$score." state: ".$state; } } $worker = new Client(); $worker->run();运行结果:上面的代码实现不难看懂,我只强调其中的一点,即 MarioStateMachine 和各个状态类之间是双向依赖关系。MarioStateMachine 依赖各个状态类是理所当然的,但是,反过来,各个状态类为什么要依赖 MarioStateMachine 呢?这是因为,各个状态类需要更新 MarioStateMachine 中的两个变量,score 和 currentState。实际上,上面的代码还可以继续优化,我们可以将状态类设计成单例,毕竟状态类中不包含任何成员变量。但是,当将状态类设计成单例之后,我们就无法通过构造函数来传递 MarioStateMachine 了,而状态类又要依赖 MarioStateMachine,那该如何解决这个问题呢?实际上我们可以通过函数参数将 MarioStateMachine 传递进状态类。根据这个设计思路,我们对上面的代码进行重构。重构之后的代码如下所示:IMario.php<?php namespace DesignPatterns\State\example2\v4; /** * Interface IMario * @package DesignPatterns\State\example2\v4 */ interface IMario { public function getName(MarioStateMachine $stateMachine); //获取蘑菇 public function obtainMushRoom(MarioStateMachine $stateMachine); //获取斗篷 public function obtainCape(MarioStateMachine $stateMachine); //获得火焰 public function obtainFireFlower(MarioStateMachine $stateMachine); //遇到怪物 public function meetMonster(MarioStateMachine $stateMachine); }SmallMario.php<?php namespace DesignPatterns\State\example2\v4; /** * Class SmallMario * @package DesignPatterns\State\example2\v3 */ class SmallMario implements IMario { private $stateMachine; private static $instances = null; private function __construct(){} public static function getInstance() { if (!isset(self::$instances)) { self::$instances = new static; } return self::$instances; } public function getName(MarioStateMachine $stateMachine) { return $stateMachine::Mario['SMALL']; } public function obtainMushRoom(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(SuperMario::getInstance()); $stateMachine->setScore($stateMachine->getScore()+100); } public function obtainCape(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(CapeMario::getInstance()); $stateMachine->setScore($stateMachine->getScore()+200); } public function obtainFireFlower(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(FireMario::getInstance()); $stateMachine->setScore($stateMachine->getScore()+300); } public function meetMonster(MarioStateMachine $stateMachine) { // do nothing... } }SuperMario.php<?php namespace DesignPatterns\State\example2\v4; class SuperMario implements IMario { private $stateMachine; private static $instances = null; private function __construct(){} public static function getInstance() { if (!isset(self::$instances)) { self::$instances = new static; } return self::$instances; } public function getName(MarioStateMachine $stateMachine) { return $stateMachine::Mario['SUPER']; } public function obtainMushRoom(MarioStateMachine $stateMachine) { // do nothing... } public function obtainCape(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(CapeMario::getInstance()); $stateMachine->setScore($stateMachine->getScore() + 200); } public function obtainFireFlower(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(FireMario::getInstance()); $stateMachine->setScore($stateMachine->getScore() + 300); } public function meetMonster(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(SmallMario::getInstance()); $stateMachine->setScore($stateMachine->getScore() -100); } }CapeMario.php<?php namespace DesignPatterns\State\example2\v4; class CapeMario implements IMario { private $stateMachine; private static $instances = null; private function __construct(){} public static function getInstance() { if (!isset(self::$instances)) { self::$instances = new static; } return self::$instances; } public function getName(MarioStateMachine $stateMachine) { return $stateMachine::Mario['CAPE']; } public function obtainMushRoom(MarioStateMachine $stateMachine) { //do nothing... } public function obtainCape(MarioStateMachine $stateMachine) { //do nothing... } public function obtainFireFlower(MarioStateMachine $stateMachine) { //do nothing... } public function meetMonster(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(SmallMario::getInstance()); $stateMachine->setScore($stateMachine->getScore() -200); } }FireMario.php<?php namespace DesignPatterns\State\example2\v4; class FireMario implements IMario { private $stateMachine; private static $instances = null; private function __construct(){} public static function getInstance() { if (!isset(self::$instances)) { self::$instances = new static; } return self::$instances; } public function getName(MarioStateMachine $stateMachine) { return $stateMachine::Mario['FIRE']; } public function obtainMushRoom(MarioStateMachine $stateMachine) { //do nothing... } public function obtainCape(MarioStateMachine $stateMachine) { //do nothing... } public function obtainFireFlower(MarioStateMachine $stateMachine) { //do nothing... } public function meetMonster(MarioStateMachine $stateMachine) { $stateMachine->setCurrentState(SmallMario::getInstance()); $stateMachine->setScore($stateMachine->getScore() -300); } }MarioStateMachine.php<?php namespace DesignPatterns\State\example2\v4; class MarioStateMachine { private $score; private $currentState; const Mario = array( 'SMALL'=>0,//小马里奥 'SUPER'=>1,//超级马里奥 'CAPE'=>2,//斗篷马里奥 'FIRE'=>3//火焰马里奥 ); public function __construct() { $this->score = 0; $this->currentState = SmallMario::getInstance(); } public function obtainMushRoom() { $this->currentState->obtainMushRoom($this); } public function obtainCape() { $this->currentState->obtainCape($this); } public function obtainFireFlower() { $this->currentState->obtainFireFlower($this); } public function meetMonster() { $this->currentState->meetMonster($this); } /** * @return int */ public function getScore(): int { return $this->score; } public function getCurrentState() { return array_search($this->currentState->getName($this),self::Mario); } /** * @param int $score */ public function setScore(int $score): void { $this->score = $score; } /** * @param IMario $currentState */ public function setCurrentState(IMario $currentState): void { $this->currentState = $currentState; } }运行 Client.php<?php namespace DesignPatterns\State\example2\v3; require dirname(dirname(__DIR__)).'/../vendor/autoload.php'; class Client { public function run() { $mario = new MarioStateMachine(); $mario->obtainMushRoom();//获取蘑菇 $mario->obtainCape();//获取斗篷 $mario->meetMonster();//遇到怪物 $score = $mario->getScore(); $state = $mario->getCurrentState(); echo "mario score: ".$score." state: ".$state; } } $worker = new Client(); $worker->run();运行结果:实际上,像游戏这种比较复杂的状态机,包含的状态比较多,优先推荐使用查表法,而状态模式会引入非常多的状态类,会导致代码比较难维护。相反,像电商下单、外卖下单这种类型的状态机,它们的状态并不多,状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能会比较复杂,所以,更加推荐使用状态模式来实现。总结今天我们讲解了状态模式。虽然网上有各种状态模式的定义,但是你只要记住状态模式是状态机的一种实现方式即可。状态机又叫有限状态机,它有 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。针对状态机,今天我们总结了三种实现方式。第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。参考文章:设计模式之美 --状态模式:游戏、工作流引擎中常用的状态机是如何实现的?github示例:https://github.com/yangpanyao/design-patterns/tree/master/State
2020年07月09日
1,590 阅读
0 评论
0 点赞
2020-07-07
PHP设计模式-职责链模式
职责链模式也叫责任链模式。职责连模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中,它是这么定义的:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。这么说比较抽象,我用更加容易理解的话来进一步解读一下。在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。职责链模式的结构与实现结构抽象处理者角色(Handler):它定义了一个处理请求的接口。当然对于链子的不同实现,也可以在这个角色中实现后继链。具体处理者角色(Concrete Handler):实现抽象角色中定义的接口,并处理它所负责的请求。如果不能处理则访问它的后继者。代码实现Handler.php<?php namespace DesignPatterns\ChainOfResponsibility\example1; /** * Handler类 抽象处理者角色 * 定义了一个处理请求的接口。当然对于链子的不同实现,也可以在这个角色中实现后继链。 * Class Handler * @package DesignPatterns\ChainOfResponsibility\example1 */ abstract class Handler { protected $successor; /** * @param mixed $successor */ public function setSuccessor(Handler $successor) { $this->successor = $successor; } //处理请求的方法 abstract public function HandleRequest($request); }ConcreteHandlerA.php<?php namespace DesignPatterns\ChainOfResponsibility\example1; /** * ConcreteHandler类,具体处理者类,处理它所负责的请求,可访问它的后继者, * 如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。 * Class ConcreteHandlerA * @package DesignPatterns\ChainOfResponsibility\example1 */ class ConcreteHandlerA extends Handler { public function HandleRequest($request) { if ($request >=0 && $request < 10) { echo "ConcreteHandlerA处理的是10到20的参数,该参数是".$request."<br>"; } else if ($this->successor != null) { // 转移 $this->successor->HandleRequest($request); } } }ConcreteHandlerB.php<?php namespace DesignPatterns\ChainOfResponsibility\example1; /** * Class ConcreteHandlerB * @package DesignPatterns\ChainOfResponsibility\example1 */ class ConcreteHandlerB extends Handler { public function HandleRequest($request) { if ($request >=10 && $request < 20) { echo "ConcreteHandlerB处理的是10到20的参数,该参数是".$request."<br>"; } else if ($this->successor != null) { // 转移 $this->successor->HandleRequest($request); } } } }ConcreteHandlerC.php<?php namespace DesignPatterns\ChainOfResponsibility\example1; /** * Class ConcreteHandlerC * @package DesignPatterns\ChainOfResponsibility\example1 */ class ConcreteHandlerC extends Handler { public function HandleRequest($request) { if ($request >=20 && $request < 30) { echo "ConcreteHandlerC处理的是20到30的参数,该参数是".$request."<br>"; } else if ($this->successor != null) { // 转移 $this->successor->HandleRequest($request); } } }运行 Client.php<?php namespace DesignPatterns\ChainOfResponsibility\example1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { $h1 = new ConcreteHandlerA(); $h2 = new ConcreteHandlerB(); $h3 = new ConcreteHandlerC(); //设置职责链的上家下家 $h1->setSuccessor($h2); $h2->setSuccessor($h3); $requests = [2,5,14,22,18,3,27,20]; //循环给最小处理者提交请求,不同的数额,由不同权限处理者处理 foreach ($requests as $value) { $h1->HandleRequest($value); } } } $worker = new Client(); $worker->run();运行结果如上例Handler 是所有处理器类的抽象父类,HandleRequest() 是抽象方法。每个具体的处理器类(ConcreteHandlerA、ConcreteHandlerB)的 HandleRequest() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 $this->successor->HandleRequest($request))。客户端需要依次实例化三个责任链实例,并指定链条成员。创建请求参数,之后通过责任链来进行结果判断。示例在 PHP 世界中, 责任链 (CoR) 模式在 HTTP 请求中间件中的使用是最广为人知的。 绝大多数流行的 PHP 框架都会使用该模式来实现 HTTP 请求中间件, 它甚至被标准化而成为了 PSR-15 的一个组成部分。它的运作方式是这样的: HTTP 请求必须通过一个中间件对象堆栈才能被程序处理。 每个中间件可以拒绝进一步处理该请求, 也可以将其传递给下一个中间件。 当请求成功通过所有中间件后, 程序的主处理者才能最终对其进行处理。你可能已经注意到这种方法在某种程度上来说颠倒了该模式的原始意图。 的确, 在通常的实现中, 只有在当前处理者无法对请求进行处理时, 请求才能沿着链进行传递; 而中间件认为程序可以处理该请求时, 才会沿着链将其传递下去。 尽管如此, 由于中间件是相互连接的, 所以整个概念仍被认为是责任链模式的示例。下面是一个使用中间件模拟验证用户登陆的一个简单示例:Middleware.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; /** * 职责链模式为组成链的对象声明了一个角色,即处理程序。 * 在我们的示例中,让我们区分中间件和最终应用程序的处理程序,后者在请求通过所有中间件时执行对象。 * 对象基本中间件类声明一个接口,用于将中间件对象链接到链中。 * Class Middleware * @package DesignPatterns\ChainOfResponsibility\example2 */ abstract class Middleware { /** * @var Middleware */ private $next; /** * 构建中间件对象链 * @param Middleware $next * @return Middleware */ public function linkWith(Middleware $next): Middleware { $this->next = $next; return $next; } /** * 子类必须重写此方法以提供自己的检查。如果子类不能处理一个请求,它可以回退到父实现。 * @param string $email * @param string $password * @return bool */ public function check(string $email, string $password): bool { if (!$this->next) { return true; } return $this->next->check($email, $password); } }UserExistsMiddleware.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; /** * 这个具体的中间件检查具有给定凭据的用户是否存在。 * Class UserExistsMiddleware * @package DesignPatterns\ChainOfResponsibility\example2 */ class UserExistsMiddleware extends Middleware { private $server; public function __construct(Server $server) { $this->server = $server; } public function check(string $email, string $password): bool { if (!$this->server->hasEmail($email)) { echo "UserExistsMiddleware: 此邮件未注册!\n"; return false; } if (!$this->server->isValidPassword($email, $password)) { echo "UserExistsMiddleware: 密码错误\n"; return false; } return parent::check($email, $password); } }RoleCheckMiddleware.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; /** * 这个具体的中间件检查与请求相关联的用户是否具有足够的权限。 * Class RoleCheckMiddleware * @package DesignPatterns\ChainOfResponsibility\example2 */ class RoleCheckMiddleware extends Middleware { public function check(string $email, string $password): bool { if ($email === "admin@example.com") { echo "RoleCheckMiddleware: Hello, admin!\n"; return true; } echo "RoleCheckMiddleware: Hello, user!\n"; return parent::check($email, $password); } }ThrottlingMiddleware.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; /** * 这个具体的中间件检查是否有太多失败的登录请求。 * Class ThrottlingMiddleware * @package DesignPatterns\ChainOfResponsibility\example2 */ class ThrottlingMiddleware extends Middleware { private $requestPerMinute;//设置可登录失败的请求次数 private $request; private $currentTime; public function __construct(int $requestPerMinute) { $this->requestPerMinute = $requestPerMinute; $this->currentTime = time(); } /** * @param string $email * @param string $password * @return bool */ public function check(string $email, string $password): bool { if (time() > $this->currentTime + 60) { $this->request = 0; $this->currentTime = time(); } $this->request++; if ($this->request > $this->requestPerMinute) { echo "ThrottlingMiddleware: 超过请求限制!\n"; die(); } return parent::check($email, $password); } }Server.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; /** * 这是一个作为实际处理程序的应用程序类。 * 在启动与请求相关联的某些业务逻辑之前,Server类使用职责链模式执行一组不同的身份验证中间件。 * Class Server * @package DesignPatterns\ChainOfResponsibility\example2 */ class Server { private $users = []; /** * @var Middleware */ private $middleware; /** * 配置中间件对象 * @param Middleware $middleware */ public function setMiddleware(Middleware $middleware): void { $this->middleware = $middleware; } /** * 服务器从客户端获取电子邮件和密码并发送对中间件的授权请求。 * @param string $email * @param string $password * @return bool */ public function logIn(string $email, string $password): bool { if ($this->middleware->check($email, $password)) { echo "Server: 授权成功!\n"; // 省略一些后续处理代码 return true; } return false; } //注册用户 public function register(string $email, string $password): void { $this->users[$email] = $password; } //判断邮箱是否存在 public function hasEmail(string $email): bool { return isset($this->users[$email]); } //验证用户密码 public function isValidPassword(string $email, string $password): bool { return $this->users[$email] === $password; } }Client.php<?php namespace DesignPatterns\ChainOfResponsibility\example2; require dirname(__DIR__).'/../vendor/autoload.php'; /** * Class Client * @package DesignPatterns\ChainOfResponsibility\example2 */ class Client { public function run() { $server = new Server(); $server->register("admin@example.com", "admin_pass"); $server->register("user@example.com", "user_pass"); //所有中间件都是链接的。客户端可以根据需要构建不同的链配置。 $middleware = new ThrottlingMiddleware(2); $middleware->linkWith(new UserExistsMiddleware($server)) ->linkWith(new RoleCheckMiddleware); $server->setMiddleware($middleware); do { echo "\nEnter your email:\n"; $email = readline(); echo "Enter your password:\n"; $password = readline(); $success = $server->logIn($email, $password); } while (!$success); } } $worker = new Client(); $worker->run();运行Client.php注意 parent::check()调用既可以插入此方法的开头,也可以插入结束。这个在所有中间件对象上提供比简单循环更大的灵活性。例如,中间件可以通过在所有其他检查之后运行其检查来更改检查顺序。也就是我们常说的前置中间件,与后置中间件。总结在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。职责链模式, 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对像处理它为止。当用户提交一个请求时,请求是沿着链传递直至有一个对象负责处理它。github示例:https://github.com/yangpanyao/design-patterns/tree/master/ChainOfResponsibility参考资料 《大话设计模式》,《深入设计模式-责任链模式》
2020年07月07日
1,246 阅读
0 评论
0 点赞
2020-07-03
PHP设计模式-策略模式
策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。策略模式的结构与实现结构代码实现Strategy.php<?php namespace DesignPatterns\Strategy\example1; /** * Strategy类,定义所有支持算法的公共接口 可以是抽象类或接口 * Class Strategy * @package DesignPatterns\Strategy\example1 */ abstract class Strategy { //算法方法 abstract public function algorithmInterface(); }ConcreteStrategyA.php<?php namespace DesignPatterns\Strategy\example1; /** * ConcreteStrategy,封装了具体的算法或行为,继承于Strategy * Class ConcreteStrategyA * @package DesignPatterns\Strategy\example1 */ class ConcreteStrategyA extends Strategy { //算法A实现方法 public function algorithmInterface() { echo "算法A实现"; } }ConcreteStrategyB.php<?php namespace DesignPatterns\Strategy\example1; class ConcreteStrategyB extends Strategy { public function algorithmInterface() { echo "算法B实现"; } }ConcreteStrategyC.php<?php namespace DesignPatterns\Strategy\example1; class ConcreteStrategyC extends Strategy { public function algorithmInterface() { echo "算法C实现"; } }Context.php<?php namespace DesignPatterns\Strategy\example1; /** * Context,用一个ConcreteStrategy来配置,维护一个对 Strategy 对象的引用。 * Class Context * @package DesignPatterns\Strategy\example1 */ class Context { private $strategy; //初始化时传入具体的策略对象 public function __construct(Strategy $strategy) { $this->strategy = $strategy; } //根据具体的策略对象调用其算法的方法 public function ContextInterface() { $this->strategy->algorithmInterface(); } }运行 Client.php<?php namespace DesignPatterns\Strategy\example1; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run() { //由于示例不用的策略最终在调用ContextInterface()所获的的结果也不尽相同 $contextA = new Context(new ConcreteStrategyA()); $contextA->ContextInterface(); echo '<br>'; $contextB = new Context(new ConcreteStrategyB()); $contextB->ContextInterface(); echo '<br>'; $contextC = new Context(new ConcreteStrategyC()); $contextC->ContextInterface(); } } $worker = new Client(); $worker->run();运行结果:策略模式的实现比较简单,我们需要定义策略抽象类或策略接口,然后让所有的具体策略实现相同的接口。然后我们在context里实例化策略类,根据需要调用不同的方法。我们可以发现策略模式和我们之前讲过的简单工厂模式 其实十分类似。不同的是,工厂相关的模式属于创建型模式,顾名思义,这种模式是用来创建对象的,返回的是new出来的对象。要调用对象的什么方法是由客户端来决定的。而策略模式属性行为型模式,通过执行上下文,将要调用的函数方法封装了起来,客户端只需要调用执行上下文的方法就可以了。示例假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。Strategy.php<?php namespace DesignPatterns\Strategy\example2; /** * 出行方式策略类 * Class Strategy * @package DesignPatterns\Strategy\example2 */ abstract class Strategy { abstract function goAirport(); }BikeStrategy.php<?php namespace DesignPatterns\Strategy\example2; /** * 自行车出行子类 * Class BikeStrategy * @package DesignPatterns\Strategy\example2 */ class BikeStrategy extends Strategy { public function goAirport() { echo '骑自行车去机场,需要2个小时,花费0元'; } }TransitStrategy.php<?php namespace DesignPatterns\Strategy\example2; /** * 公交出行子类 * Class TransitStrategy * @package DesignPatterns\Strategy\example2 */ class TransitStrategy extends Strategy { public function goAirport() { echo '坐公交去机场,需要1小时,花费2元'; } }TaxiStrategy.php<?php namespace DesignPatterns\Strategy\example2; class TaxiStrategy extends Strategy { public function goAirport() { echo '坐出租车去机场,需要0.5小时,花费30元'; } }Context.php<?php namespace DesignPatterns\Strategy\example2; class Context { private $strategy; public function __construct(Strategy $strategy) { $this->strategy = $strategy; } public function ContextInterface() { $this->strategy->goAirport(); } }运行Client.php<?php namespace DesignPatterns\Strategy\example2; require dirname(__DIR__).'/../vendor/autoload.php'; class Client { public function run($type){ switch ($type){ case 'bike': $TravelStrategy = new Context(new BikeStrategy()); break; case 'transit': $TravelStrategy = new Context(new TransitStrategy()); break; case 'taxi': $TravelStrategy = new Context(new TaxiStrategy()); break; default: throw new \Exception("不支持的方式"); break; } $TravelStrategy->ContextInterface(); } } $worker = new Client(); $worker->run('bike');运行结果:在本例中,我们需要在客户端根据不同的类型中 实例化 Context 以及实例化对应的具体策略。客户端与Context以及具体策略耦合在一起。如何减少客户端的耦合呢?其实我们可是策略模式与简单工厂相结合来解决这个问题策略模式与简单工厂相结合class ContextFactory { private $strategy; // public function __construct($type) { switch ($type){ case 'bike': $this->strategy = new BikeStrategy(); break; case 'transit': $this->strategy = new TransitStrategy(); break; case 'taxi': $this->strategy = new TaxiStrategy(); break; default: throw new \Exception("不支持的方式"); break; } } //根据具体的策略对象调用其算法的方法 public function ContextInterface() { $this->strategy->algorithmInterface(); } }更改前,客户端策略模式与简单工厂结合后,具体的出行算法与客户端分离,客户端只需要认识Context,耦合性相对更低。总结策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中公共功能。当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个Strategy类中,可以在使用这些行为的类中消除条件语句。策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。github示例:https://github.com/yangpanyao/design-patterns/tree/master/Strategy
2020年07月03日
1,434 阅读
0 评论
0 点赞
1
2
3