菜单

PHP设计模式之状态模式定义与用法详解_php技巧_脚本之家

2020年2月7日 - 首页

本文实例讲述了PHP设计模式之状态模式定义与用法。分享给大家供大家参考,具体如下:

本文实例讲述了PHP设计模式之适配器模式定义与用法。分享给大家供大家参考,具体如下:

什么是状态设计模式

适配器很容易理解, 大多数人家庭都有手机转接器,
用来为移动电话充电,这就是一种适配器. 如果只有USB接头,
就无法将移动电话插到标准插座上. 实际上, 必须使用一个适配器,
一端接USB插头, 一端接插座. 当然, 你可以拿出电气工具,改装USB连接头,
或者重新安装插座, 不过这样会带来很多额外的工作,
而且可能会把连接头或插座弄坏. 所以, 最可取的方法就是找一个适配器.
软件开发也是如此.

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

类适配器模式

澳门太阳集团,状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

类适配器模式很简单, 不过与对象适配器模式相比, 类适配器模式的灵活性弱些,
类适配器简单的原因在于 , 适配器会从被适配者继承功能,
所以适配模式中需要编写的代码比较少.

什么时候使用状态模式

由于类适配器模式包含双重继承, 但是PHP并不支持双重继承,
不过幸运的是,PHP可以用接口来模拟双重继承, 下面是一个正确的结构,
不仅继承了一个类, 同时还继承了一个接口

对象中频繁改变非常依赖于条件语句。 就其自身来说,
条件语句本身没有什么问题,不过, 如果选项太多, 以到程序开始出现混乱,
或者增加或改变选项需要花费太多时间, 甚至成为一种负担, 这就出现了问题

class ChildClass extends ParentClass implements ISomeAdapter{}

对于状态设计模式, 每个状态都有自己的具体类, 它们实现一个公共接口.
我们不用查看对象的控制流, 而是从另一个角度来考虑, 即对象的状态.

实现类适配器模式时, 参与者必须包括一个PHP接口

状态机是一个模型, 其重点包括不同的状态, 一个状态到另一个状态的变迁,
以及导致状态改变的触发器.

下面以一个货币兑换为例来演示:

以开灯关灯为例子, 状态模型的本质分为3点:

假设有一个企业网站在同时销售软件服务和软件产品, 目前,
所有交易都在美国进行,
所以完全可以用美元来完成所有计算.现在开发人员希望能有一个转换器能处理美元和欧元的兑换,
而不改变原来按美元交易额的类.通过增加一个适配器,
现在程序即可以用美元计算也可以用欧元计算.

所以状态模式都需要一个参与者来跟踪对象所处的状态. 以Light为例,
Light需要知道当前状态是什么.

DollarCalc.php

示例:开灯关灯

product = $productNow; $this->service = $serviceNow; $this->dollar = $this->product + $this->service; return $this->requestTotal(); } public function requestTotal() { $this->dollar *= $this->rate; return $this->dollar; }}
offState = new OffState; $this->onState = new OnState; //开始状态为关闭状态Off $this->currentState = $this->offState; } //调用状态方法触发器 public function turnLightOn() { $this->currentState->turnLightOn(); } public function turnLightOff() { $this->currentState->turnLightOff(); } //设置当前状态 public function setState { $this->currentState = $state; } //获取状态 public function getOnState() { return $this->onState; } public function getOffState() { return $this->offState; }}

查看这个类,可以看到其中有一个属性$rate,requestTotal()方法使用$rate计算一次交易的金额.在这个版本中,
这个值设置为1,实际上总金额无需再乖以兑换率,
不过如果要为客户提供折扣或者要增加额外服务或产品的附加费,
$rate变量会很方便. 这个类并不是适合器模式的一部分, 不过这是一个起点.

在构造函数中, Light实例化IState实现的两个实例—–一个对应关, 一个对应开

需求变化了

$this->offState = new OffState;$this->onState = new OnState;

现在客户的公司要向欧洲发展,所以需要开发一个应用,
能够用欧元完成同样的计算. 你希望这个欧元计算能够像DollarCalc一样,
所要做的就是改变变量名.

这个实例化过程用到了一种递归, 称为自引用

EuroCalc.php

构造函数参数中的实参写为$this, 这是Light类自身的一个引用.
状态类希望接收一个Light类实例做参数,.

product = $productNow; $this->service = $serviceNow; $this->euro = $this->product + $this->service; return $this->requestTotal(); } public function requestTotal() { $this->euro *= $this->rate; return $this->euro; }}

setState方法是为了设置一个当前状态 需要一个状态对象作为实参,
一旦触发一个状态, 这个状态就会向Light类发送信息, 指定当前状态.

接下来, 再把应用的其余部分插入到EuroCalc类中.
不过,因为客户的所有数据都是按美元计算的.换句话说,
如果不重新开发整个程序, 就无法在系统中”插入”这个欧元计算.
但是你不想这么做. 为了加入EuroCalc, 你需要一个适配器:
就像找一个适配器来适应欧洲的插座一样, 可以创建一个适配器,
使你的系统能够使用欧元. 幸运的是,
类适配器正是为这样的情况设计的.首先需要创建一个接口. 在这个类图中,
这个接口名为ITarget.
它只有一个方法requester是一个抽象方法, 要由接口的具体实现来实现这个方法.

状态实例

ITarget.php

IState.php

现在开发人员可以实现requester()方法, 请求欧元而不是美元.在使用继承的适配器设计模式中, 适配器参与都既实现ITarget接口,还实现了具体类EuroCalc. 创建EuroAdapter不需要做太多工作, 因为大部分工作已经在EuroCal类中完成.现在要做的就是实现request()方法, 使它能把美元值转换为欧元值.EuroAdapter.phprequester(); } public function requester() { $this->rate = 0.8111; return $this->rate; }}
OnState.phplight = $light; } public function turnLightOn() { echo "灯已经打开了->不做操作
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState; }}

类适配模式中, 一个具体类会继承另一个具体类, 有这种结构的设计模式很少见,
大多数设计模式中, 几乎都是继承一个抽象类,
并由类根据需要实现其抽象方法和属性. 换句话说, 一般谈到继承时,
都是具体类继承抽象类.

OffState.php

由于既实现了一个接口又扩展了一个类,
所以EuroAdapter类同时拥有该接口和具体类的接口. 通过使用requester()方法,
EuroAdapter类可以设置rate值, 从而能使用被适配者的功能, 而元而做任何改变.

light = $light; } public function turnLightOn() { echo "灯打开!可以看见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOnState; } public function turnLightOff() { echo "灯已经关闭了->不做操作
"; }}

下面定义一个Client类, 从EuroAdapter和DollarCalc类发出请求.
可以看到,原来的DollarCalc仍能很好地工作, 不过它没有ITarget接口.

默认状态是OffState, 它必须实现IState方法turnLightOn和turnLightOff,
Light调用turnLightOn方法, 会显示(灯打开!可以看见帅哥chenqionghe了),
然后将OnState设置为当前状态, 不过,如果是调用 OffState的turnLightOff方法,
就只有提示灯已经被关闭了 不会有其他动作.

Client.php

客户

makeApapterRequest . '
'; echo "美元: $: " . $this->makeDollarRequest . '
'; } private function makeApapterRequest { return $req->requestCalc; } private function makeDollarRequest { return $req->requestCalc; }}$woker = new Client();

Client的所有请求都是通过Light发出, Client和任何状态类之间都没有直接连接,
包括IState接口.下面的Client显示了触发两个状态中所有方法的请求.

Euros: ?72.999Dollars: $: 90

Client.php

可以看到,美元和欧元都可以处理, 这就是适配器模式的方便之处.

light = new Light(); $this->light->turnLightOn(); $this->light->turnLightOn(); $this->light->turnLightOff(); $this->light->turnLightOff(); }}$worker = new Client();

这个计算很简单, 如果是针对更为复杂的计算,
继承要提供建立类适配器的Target接口的必要接口和具体实现

增加状态

使用组合的适配器模式

对于所有的设计模式来说,很重要的一个方面是:
利用这些设计模式可以很容易地做出修改.
与其他模式一样,状态模式也很易于更新和改变.
下面在这个灯的示例上再加两个状态:更亮和最亮

对象适配器模式使用组合而不是继承, 不过它也会完成同样的目标.
通过比较这两个版本的适配器模式, 可以看出它们各自的优缺点.
采用类适配器模式时,适配器可以继承它需要的大多数功能, 只是通过接口稍微调.
在对象适配器模式中 适配器参与使用被适配者, 并实现Target接口.
在类适配器模式中, 适配器则是一个被适配者, 并实现Target接口.

现在变成了4个状态, 序列有所改变. ‘关’状态, on状态不能变到off状态.
on状态只能变到”更亮”状态和”最亮”状态. 只能最亮状态才可能变到关状态.

示例: 从桌面环境转向移动环境

改变接口

PHP程序员经常会遇到这样一个问题:需要适应移动环境而做出调整.不久之前,你可能只需要考虑提供一个网站来适应多种不同的桌面环境.
大多数桌面都使用一个布局, 再由设计人员让它更美观. 对于移动设备,
设计人员和开发人员不仅需要重新考虑桌面和移动环境中页面显示的设计元素,
还要考虑如何从一个环境切换到另一个环境.

要改变的第一个参与者是接口IState, 这个接口中必须指定相应的方法,
可以用来迁移到brighter和brightest状态.

首先来看桌面端的类Desktop. 这个类使用了一个简单但很宽松的接口:

IState.php

IFormat.php

现在所有状态类都必须包含这4个方法, 它们都需要结合到Light类中.改变状态状态设计模式中有改变时, 这些新增的改变会对模式整体的其他方面带来影响. 不过, 增加改变相当简单, 每个状态只有一个特定的变迁.四个状态OnState.phplight = $light; } public function turnLightOn() { echo "不合法的操作!
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState; } public function turnBrighter() { echo "灯更亮了, 看帅哥chenqionghe看得更真切了!
"; $this->light->setState($this->light->getBrighterState; } public function turnBrightest() { echo "不合法的操作!
"; }}
它支持css和图片选择, 不过其中一个方法指示一种水平布局, 我们知道这种布局并不适用小的移动设备.下面给出实现这个接口的Desktop类Desktop.php"; } public function formatGraphics() { echo "引用desktop.png图片
"; } public function horizontalLayout() { echo '桌面:水平布局'; }}

OffState.php

问题来了, 这个布局对于小的移动设备来说太宽了.
所以我们的目标是仍采用同样的内容, 但调整为一种移动设计.

light = $light; } public function turnLightOn() { echo "灯打开!可以看见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOnState; } public function turnLightOff() { echo "不合法的操作!
"; } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "不合法的操作!
"; }}

下面来看移动端的类Mobile

Brighter.php

首先移动端有一个移动端的接口

light = $light; } public function turnLightOn() { echo "不合法的操作!
"; } public function turnLightOff() { echo "不合法的操作!
"; } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "灯最亮了, 看帅哥chenqionghe已经帅到无敌!
"; $this->light->setState($this->light->getBrightestState; }}

IMobileFormat

Brightest.php

可以看到, IMobileFormat接口和IFormat接口是不一样的,也就是不兼容的, 一个包含了方法horizontalLayout(), 另一个包含方法verticalLaout(), 它们的差别很小, 最主要的区别是: 桌面设计可以采用水平的多栏布局, 而移动设计要使用垂直布局,而适配器就是要解决这个问题下面给出一个实现了IMoibleFormat接口的Mobile类Mobile.php"; } public function formatGraphics() { echo "引用mobile.png图片
"; } public function verticalLayout() { echo '移动端:垂直布局'; }}
light = $light; } public function turnLightOn() { echo "灯已经打开了->不做操作
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState; } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "不合法的操作!
"; }}

Mobile类和Desktop类非常相似, 不过是图片和CSS引用不同

更新Light类

接下来,我们需要一个适配器,将Desktop和Mobile类结合在一起

Light.php

MobileAdapter.php

offState = new OffState; $this->onState = new OnState; $this->brighterState = new BrighterState; $this->brightestState = new BrightestState; //开始状态为关闭状态Off $this->currentState = $this->offState; } //调用状态方法触发器 public function turnLightOn() { $this->currentState->turnLightOn(); } public function turnLightOff() { $this->currentState->turnLightOff(); } public function turnLightBrighter() { $this->currentState->turnBrighter(); } public function turnLigthBrightest() { $this->currentState->turnBrightest(); } //设置当前状态 public function setState { $this->currentState = $state; } //获取状态 public function getOnState() { return $this->onState; } public function getOffState() { return $this->offState; } public function getBrighterState() { return $this->brighterState; } public function getBrightestState() { return $this->brightestState; }}
mobile = $mobileNow; } public function formatCSS() { $this->mobile->formatCSS(); } public function formatGraphics() { $this->mobile->formatGraphics(); } public function horizontalLayout() { $this->mobile->verticalLayout(); }}

更新客户

可以看到,MobileAdapter实例化时要提供一个Mobile对象实例.还要注意
,类型提示中使用了IMobileFormat, 确保参数是一个Mobile对象.有意思的是,
Adapter参与者通过实现horizontalLayout()方法来包含verticalLayout()方法.实际上,
所有MobileAdapter方法都包装了一个Mobile方法.碰巧的是,
适配器参与者中的一个方法并不在适配器接口中(verticalLayout;它们可能完全不同, 适配器只是把它们包装在适配器接口的某一方法中.

light = new Light(); $this->light->turnLightOn(); $this->light->turnLightBrighter(); $this->light->turnLigthBrightest(); $this->light->turnLightOff(); $this->light->turnLigthBrightest(); }}$worker = new Client();

客户调用

灯打开!可以看见帅哥chenqionghe了!灯更亮了,
看帅哥chenqionghe看得更真切了!灯最亮了,
看帅哥chenqionghe已经帅到无敌!灯关闭!看不见帅哥chenqionghe了!不合法的操作!

Client.php

九宫格移动示例

mobile = new Mobile(); $this->mobileAdapter = new MobileAdapter; $this->mobileAdapter->formatCSS(); $this->mobileAdapter->formatGraphics(); $this->mobileAdapter->horizontalLayout(); }}$worker = new Client();

九宫格的移动分为4个移动:

适配器模式中的Client类必须包装Adaptee的一个实例,
以便集成到Adapter本身.实例化Adapter时,
Client使用Adatee作为参数来完成Adapter的实例化.所以客户必须首先创建一个Adapter对象),
然后创建一个Adapter((new MobileAdapter.

对于这些移动,规则是要求单元格之间不能沿对角线方向移动. 另外,
从一个单元格移动到下一个单元格时, 一次只能移动一个单元格

Client类的大多数请求都是通过MobileAdapter发出的.
不过这个代码的最后他使用了Mobile类的实例.

要使用状态设计模式来建立一个九宫格移动示例,

适配器和变化

建立接口

PHP程序员要即该面对变化.不同版本的PHP会变化, 可能增加新的功能,
另外还可能取消一些功能.而且随着PHP的大大小小的变化,MySQL也在改变.例如,
mysql的扩展包升级为mysqli, PHP开发人员需要相应调整,
要改为使用mysqli中的新API.这里适合采用适配器模式吗?可能不适合.适配器可能适用,
可能不适用,这取决于你的程序如何配置.当然可以重写所有连接和交互代码,
不过这可不是适配器模式的本意, 这就像是重新安装USB连接头,
想把它插进标准的墙上插座一样. 不过, 如果所有原来的mysql代码都在模块中,
你可以修改这个模块,换入一个有相同接口的新模块.只是要使用mysqli而不是mysql.我不认为交换等同于适配器,
不过道理是一样的, 在适配器模式中, 原来的代码没有任何改变,
有变化的只是适配器.

IMatrix.php

如果需要结合使用两个不兼容的接口, 这种情况下,
适配器模式最适用.适配器可以完成接口的”联姻”.可以把适配器看作是一个婚姻顾问;通过创建一个公共接口来克服双方的差异.利用
这种设计模式, 可以促成二者的合作,而避免完全重写某一部分.

虽然这个状态设计模式有9个状态, 分别对应九个单元格, 但一个状态最多只需要4个变迁上下文对于状态中的4个变迁或移动方法, 上下文必须提供相应方法来调用这些变迁方法, 另外还要完成各个状态的实例化.Context.phpcell1 = new Cell1State; $this->cell2 = new Cell2State; $this->cell3 = new Cell3State; $this->cell4 = new Cell4State; $this->cell5 = new Cell5State; $this->cell6 = new Cell6State; $this->cell7 = new Cell7State; $this->cell8 = new Cell8State; $this->cell9 = new Cell9State; $this->currentState = $this->cell5; } //调用方法 public function doUp() { $this->currentState->goUp(); } public function doDown() { $this->currentState->goDown(); } public function doLeft() { $this->currentState->goLeft(); } public function doRight() { $this->currentState->goRight(); } //设置当前状态 public function setState { $this->currentState = $state; } //获取状态 public function getCell1State() { return $this->cell1; } public function getCell2State() { return $this->cell2; } public function getCell3State() { return $this->cell3; } public function getCell4State() { return $this->cell4; } public function getCell5State() { return $this->cell5; } public function getCell6State() { return $this->cell6; } public function getCell7State() { return $this->cell7; } public function getCell8State() { return $this->cell8; } public function getCell9State() { return $this->cell9; }}

更多关于PHP相关内容感兴趣的读者可查看本站专题:《php面向对象程序设计入门教程》、《PHP基本语法入门教程》、《PHP数组操作技巧大全》、《php字符串用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

状态

希望本文所述对大家PHP程序设计有所帮助。

9个状态表示九宫格中的不同单元格,
为了唯一显示单元格,会分别输出相应到达的单元格数字,
这样能够更清楚地看出穿过矩阵的路线.

Cell1State

context = $contextNow; } public function goLeft() { echo '不合法的移动!
'; } public function goRight() { echo '走到2
'; $this->context->setState($this->context->getCell2State; } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到4
'; $this->context->setState($this->context->getCell4State; }}

Cell2State

context = $contextNow; } public function goLeft() { echo '走到1
'; $this->context->setState($this->context->getCell1State; } public function goRight() { echo '走到3
'; $this->context->setState($this->context->getCell3State; } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到5
'; $this->context->setState($this->context->getCell5State; }}

Cell3State

context = $contextNow; } public function goLeft() { echo '走到2
'; $this->context->setState($this->context->getCell2State; } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到6
'; $this->context->setState($this->context->getCell6State; }}

Cell4State

context = $contextNow; } public function goLeft() { echo '不合法的移动!
'; } public function goRight() { echo '走到5
'; $this->context->setState($this->context->getCell5State; } public function goUp() { echo '走到1
'; $this->context->setState($this->context->getCell1State; } public function goDown() { echo '走到7
'; $this->context->setState($this->context->getCell7State; }}

Cell5State

context = $contextNow; } public function goLeft() { echo '走到4
'; $this->context->setState($this->context->getCell4State; } public function goRight() { echo '走到6
'; $this->context->setState($this->context->getCell6State; } public function goUp() { echo '走到2
'; $this->context->setState($this->context->getCell2State; } public function goDown() { echo '走到8
'; $this->context->setState($this->context->getCell8State; }}

Cell6State

context = $contextNow; } public function goLeft() { echo '走到5
'; $this->context->setState($this->context->getCell5State; } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '走到3
'; $this->context->setState($this->context->getCell3State; } public function goDown() { echo '走到9
'; $this->context->setState($this->context->getCell9State; }}

Cell7State

context = $contextNow; } public function goLeft() { echo '不合法的移动!
'; } public function goRight() { echo '走到8
'; $this->context->setState($this->context->getCell8State; } public function goUp() { echo '走到4
'; $this->context->setState($this->context->getCell4State; } public function goDown() { echo '不合法的移动!
'; }}

Cell8State

context = $contextNow; } public function goLeft() { echo '走到7
'; $this->context->setState($this->context->getCell7State; } public function goRight() { echo '走到9
'; $this->context->setState($this->context->getCell9State; } public function goUp() { echo '走到5
'; $this->context->setState($this->context->getCell5State; } public function goDown() { echo '不合法的移动!
'; }}

Cell9State

context = $contextNow; } public function goLeft() { echo '走到8
'; $this->context->setState($this->context->getCell8State; } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '走到6
'; $this->context->setState($this->context->getCell6State; } public function goDown() { echo '不合法的移动!
'; }}

要想有效地使用状态设计模式, 真正的难点在于要想象现实或模拟世界是怎么样

客户Client

下面从单元格5开始进行一个上,右,下, 下,左,上的移动

Client.php

context = new Context(); $this->context->doUp(); $this->context->doRight(); $this->context->doDown(); $this->context->doDown(); $this->context->doLeft(); $this->context->doUp(); }}$worker = new Client();

走到2走到3走到6走到9走到8走到5

状态模式与PHP

很多人把状态设计模式看做是实现模拟器和游戏的主要方法.总的说来,
这确实是状态模式的目标,不过险些之外,
状态模型和状态设计模式在PHP中也有很多应用.用PHP完成更大的项目时,
包括Facebook和WordPress,
会有更多的新增特性和当前状态需求.对于这种不断有改变和增长的情况,
就可以采用可扩展的状态模式来管理.

PHP开发人员如何创建包含多个状态的程序, 将决定状态模式的使用范围.
所以不仅状态机在游戏和模拟世界中有很多应用,
实际上状态模型还有更多适用的领域.只要PHP程序的用户会用到一组有限的状态,
开发人员就可以使用状态设计模式.

更多关于PHP相关内容感兴趣的读者可查看本站专题:《php面向对象程序设计入门教程》、《PHP基本语法入门教程》、《PHP数组操作技巧大全》、《php字符串用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总》

希望本文所述对大家PHP程序设计有所帮助。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图