首页 理论教育 大话设计模式:命令模式下的饭店点菜

大话设计模式:命令模式下的饭店点菜

时间:2023-11-03 理论教育 版权反馈
【摘要】:第二十三章饭店点菜——命令模式23.1饭店点菜时间:1月8日地点:肥羊王人物:大B和他的女朋友,小A这天,小A约了大B和他的女朋友一起去肥羊王吃火锅。先来看下命令角色——Action控制类下面的就是请求者角色,它仅仅负责调用命令角色执行操作。

大话设计模式:命令模式下的饭店点菜

第二十三章 饭店点菜——命令模式

23.1 饭店点菜

时间:1月8日  地点:肥羊王  人物:大B和他的女朋友,小A

这天,小A约了大B和他的女朋友一起去肥羊王吃火锅。刚到坐定,一个笑得挺甜美的服务生来了。

肥羊王服务生:“先生,小姐吃点什么?”

大B的女朋友:“来个鸳鸯火锅。”

肥羊王服务生:“好的。请问还要点什么菜?”

大B的女朋友:“来两盘肥羊、猪脑花、鸭血、水发海带白菜粉丝白萝卜豆腐金针菇鱼丸、猪肉丸、牛肉丸各一碟。先上这些,不够我一会再点。”

肥羊王服务生:“好的,马上来,请各位稍等!”

大B的女朋友:“等等,再来三碟辣椒酱。”

肥羊王服务生:“好的,请各位稍等!”

23.2 命令模式

每次和大B的女朋友吃饭都是她负责点菜的,嘿嘿,我们负责吃,每次都吃得饱饱的。

大B:“别光顾着吃,这就是我们讲的命令模式?”

小A:“命令模式?”

大B:“将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。”

23.3 命令模式涉及到哪些角色

小A:“命令模式涉及到哪些角色?”

大B:“1、命令角色(Command):声明执行操作的接口。有Java接口或者抽象类来实现。2、具体命令角色(Concrete Command):将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现命令角色声明的执行操作的接口。3、客户角色(Client):创建一个具体命令对象(并可以设定它的接收者)。4、请求者角色(Invoker):调用命令对象执行这个请求。5、接收者角色(Receiver):知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。”

23.4  Struts中Action的使用

大B:“由于在JUnit中,参杂了其它的模式在里面,使得命令模式的特点不太明显。我给你讲讲以命令模式在Web开发中最常见的应用——Struts中Action的使用作为例子。”

小A:“嗯。好的。”

大B:“在Struts中Action控制类是整个框架的核心,它连接着页面请求和后台业务逻辑处理。按照框架设计,每一个继承自Action的子类,都实现execute方法——调用后台真正处理业务的对象来完成任务。”

大B:“需要注意的是:继承自DispatchAction的子类,则可以一个类里面处理多个类似的操作。”

下面我们将Struts中的各个类与命令模式中的角色对号入座。

先来看下命令角色——Action控制类

下面的就是请求者角色,它仅仅负责调用命令角色执行操作。

大B:“Struts框架为我们提供了以上两个角色,要使用struts框架完成自己的业务逻辑,剩下的三个角色就要由我们自己来实现了。”

小A:“那要怎么去实现啊?”

大B:“实现的步骤如下:1、很明显我们要先实现一个Action的子类,并重写execute方法。在此方法中调用业务模块的相应对象来完成任务。2、实现处理业务的业务类。3、配置struts-config.xml配置文件,将自己的Action和Form以及相应页面结合起来。4、编写jsp,在页面中显式的制定对应的处理Action。”

23.5 Undo、事务及延伸

大B:“在定义中提到,命令模式支持可撤销的操作。”

小A:“但是在上面的举例中并没有体现出来啊。”

大B:“其实命令模式之所以能够支持这种操作,完全得益于在请求者与接收者之间添加了中间角色。为了实现undo功能,首先需要一个历史列表来保存已经执行过的具体命令角色对象;修改具体命令角色中的执行方法,使它记录更多的执行细节,并将自己放入历史列表中;并在具体命令角色中添加undo方法,此方法根据记录的执行细节来复原状态,很明显,首先程序员要清楚怎么来实现,因为它和execute的效果是一样的。同样,redo功能也能够照此实现。命令模式还有一个常见的用法就是执行事务操作。这就是为什么命令模式还叫做事务模式的原因吧。它可以在请求被传递到接收者角色之前,检验请求的正确性,甚至可以检查和数据库中数据的一致性,而且可以结合组合模式的结构,来一次执行多个命令。使用命令模式不仅仅可以解除请求者和接收者之间的耦合,而且可以用来做批处理操作,这完全可以发挥你自己的想象——请求者发出的请求到达命令角色这里以后,先保存在一个列表中而不执行;等到一定的业务需要时,命令模式再将列表中全部的操作逐一执行。”

小A:“哦,命令模式实在太灵活了。真是一个很有用的东西啊!”

23.6 优点

小A:“命令模式都有哪些优点呐?”

大B:“从刚才我和你讲的内容可以看出命令模式有以下优点:1、 命令模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦。2、具体命令角色可以被不同的请求者角色重用。3、 你可将多个命令装配成一个复合命令。4、增加新的具体命令角色很容易,因为这无需改变已有的类。”

23.7 命令模式的适用环境

小A:“命令模式的适用哪些环境?”

大B:“1、需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制。而命令模式正是回调机制的一个面向对象的替代品。2、在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。3、需要支持取消操作。4、支持修改日志功能。这样当系统崩溃时,这些修改可以被重做一遍。5、需要支持事务操作。”

在此写了7个Java类来描述说明Command设计模式的实现方式;

1、 Control.java 命令控制者对象类

2、 Tv.java 命令接收者对象类

3、 Command.java 命令接口类

4、 CommandChannel.java 频道切换命令类

5、 CommandOff.java 关机命令类(www.xing528.com)

6、 CommandOn.java 开机命令类

7、 CommandTest.java 带有main方法的测试类(命令发送者)

=============== 1、 Control.java

=============== 1 end

=============== 2、 Tv.java

=============== 2 end

=============== 3、 Command.java

=============== 3 end

=============== 4、 CommandChannel.java

=============== 4 end

=============== 5、 CommandOff.java

=============== 5 end

=============== 6、 CommandOn.java

=============== 6 end

=============== 7、 CommandTest.java

=============== 7 end

23.8 Command模式应用场景

大B:“Command模式通常可应用到以下场景:1、Multi-level undo(多级undo操作)如果系统需要实现多级回退操作,这时如果所有用户的操作都以command对象的形式实现,系统可以简单地用stack来保存最近执行的命令,如果用户需要执行undo操作,系统只需简单地popup一个最近的command对象然后执行它的undo()方法既可。2、Transactional behavior(原子事务行为)借助command模式,可以简单地实现一个具有原子事务的行为。当一个事务失败时,往往需要回退到执行前的状态,可以借助command对象保存这种状态,简单地处理回退操作。3、Progress bars(状态条)假如系统需要按顺序执行一系列的命令操作,如果每个command对象都提供一个getEstimatedDuration()方法,那么系统可以简单地评估执行状态并显示出合适的状态条。 4、 Wizards(导航)通常一个使用多个wizard页面来共同完成一个简单动作。一个自然的方法是使用一个command对象来封装wizard过程,该command对象在第一个wizard页面显示时被创建,每个wizard页面接收用户输入并设置到该command对象中,当最后一个wizard页面用户按下‘Finish’按钮时,可以简单地触发一个事件调用execute()方法执行整个动作。通过这种方法,command类不包含任何跟用户界面有关的代码,可以分离用户界面与具体的处理逻辑。5、GUI buttons and menu items(GUI按钮与菜单条等等)Swing系统里,用户可以通过工具条按钮,菜单按钮执行命令,可以用command对象来封装命令的执行。6、Thread pools(线程池)通常一个典型的线程池实现类可能有一个名为addTask()的public方法,用来添加一项工作任务到任务队列中。该任务队列中的所有任务可以用command对象来封装,通常这些command对象会实现一个通用的接口比如Java.lang.Runnable。 7、Macro recording(宏纪录)可以用command对象来封装用户的一个操作,这样系统可以简单通过队列保存一系列的command对象的状态就可以记录用户的连续操作。这样通过执行队列中的command对象,就可以完成‘Play back’操作了。8、 Networking通过网络发送command命令到其他机器上运行。9、Parallel Processing(并发处理)当一个调用共享某个资源并被多个线程并发处理时。”

23.9 命令模式的实现

小A:“命令模式怎样去实现它?”

大B:“命令模式里边一般都有以下几个角色:客户端,请求者,命令接口,命令实现,接受者。下边是简单命令模式的实现代码实现。”

23.10 命令模式的功能,好处,或者说为什么使用命令模式?

大B:“上边的代码是否看起来很傻呢,本来可以这样简单实现的。”

大B:“看多简洁,如果是像上边如此简单的需求,这个才应该是我们的选择,但是有些情况下这样的写法不能解决的,或者说解决起来不好,所以引入命令模式。1、我们须要Client和Receiver同时开发,而且在开发过程中分别须要不停重购,改名。2、如果我们要求Redo ,Undo等功能。3、我们须要命令不按照调用执行,而是按照执行时的情况排序,执行。4、开发后期,我们发现必须要log哪些方法执行了,如何在尽量少更改代码的情况下实现.并且渐少重复代码。5、在上边的情况下,我们的接受者有很多,不止一个。”

小A:“当我们遇到这些情况时应该怎样去解决?”

大B:“解决办法:情况一、我们可以定义一个接口,让Receiver实现这个接口,Client按照接口调用。情况二、我们可以让Receiver记住一些状态,例如执行前的自己的状态,用来undo,但自己记录自己的状态 实现起来比较混乱,一般都是一个累记录另一个类的状态。情况三、很难实现。情况四、我们须要在每个Action,前后加上log。”

小A:“情况五、相对好实现,但是再加上这个,是否感觉最终的实现很混乱呢?”

大B:“好,我们再来看看命令模式,在命令模式中,我们增加一些过渡的类,这些类就是上边的命名接口和命令实现,这样就很好的解决了情况一、情况二。我们再加入一个Invoker,这样情况三和情况四就比较好解决了。”

如下加入Log和排序后的Invoker

23.11 命令模式与其它模式的配合使用

小A:“命令模式怎样与其它模式的配合使用?”

大B:“1、看上边的Invoker的实现是否很像代理模式呢,Invoker的这种实现其实就是一种代理模式。2、需求:有个固定命令组合会多次被执行。解决:加入合成模式,实现方法如下,定义一个宏命令类。

3、需求:须要redo undo解决:加入备忘录模式,一个简单的实现如下

4、需求:命令很多类似的地方

解决:使用原型模式,利用clone

23.12 足球

大B:“为了把命令模式讲清楚,我举一个印象深刻的例子以便理解,那就借用的足球的例子吧。”

小A:“好的。”

大B:“我设计了五个类,分别是:球队老板,老板的命令(接口),教练,命令的内容,球员。”

球员的示例代码

教练类的示例代码

老板的命令类的示例代码

教练的示例代码

老板的示例代码

大B:“在实际的应用中老板就相当于用户本人,球员相当于具体的实施类,在具体的实施类里面有n多的方法,你可以通过一个命令类来表明你要的操作,而不是老板类来直接来控制球员类,其中的顺序是这样的:老板发出命令给教练,教练根据命令中的具体内容给球员,球员作出行为给老板挣钱,这就是老板的命令模式,哈哈,要是老板要打假球就发出drillmaster.fail();//输球这样的命令就行了。可怜的球迷呀!最后被骂的还是教练和球员,老板要是实在看不下去了,就发出 drillmaster.victory();//赢球 就可以了,被媒体吹捧的是教练和球员,而老板有了钱,但我们可不知道内幕的原因,因为看起来老板没有参加实际的操作。”

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈