﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-I smell magic in the air-随笔分类-设计模式</title><link>http://www.cppblog.com/izualzhy/category/18163.html</link><description>坚持 相信自己</description><language>zh-cn</language><lastBuildDate>Wed, 16 Nov 2011 20:44:30 GMT</lastBuildDate><pubDate>Wed, 16 Nov 2011 20:44:30 GMT</pubDate><ttl>60</ttl><item><title>设计模式之命令模式</title><link>http://www.cppblog.com/izualzhy/archive/2011/11/15/160199.html</link><dc:creator>izualzhy</dc:creator><author>izualzhy</author><pubDate>Tue, 15 Nov 2011 13:41:00 GMT</pubDate><guid>http://www.cppblog.com/izualzhy/archive/2011/11/15/160199.html</guid><wfw:comment>http://www.cppblog.com/izualzhy/comments/160199.html</wfw:comment><comments>http://www.cppblog.com/izualzhy/archive/2011/11/15/160199.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/izualzhy/comments/commentRss/160199.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/izualzhy/services/trackbacks/160199.html</trackback:ping><description><![CDATA[<p>设计模式之命令行模式 
<p>《设计模式-可复用面向对象软件的基础》学习笔记 
<p>有理解or代码不对的地方请指出。 
<p>对象行为型模式之一，名称有很多Command,Action,Transaction&#8230;.. 
<p>将<strong>一个请求封装为一个对象</strong>，从而使你可<strong>用不同的请求对客户端进行参数</strong>化；对<strong>请求排队或记录请求日志</strong>，以及<strong>支持可撤销</strong>的操作。 
<p>动机： 
<p>如果没法联网又想玩dota，只好和电脑AI自娱自乐下。 
<p>实现： 
<p>我们可以给AI下达各种命令，如去打Roshan，集合，去捡神符等等。 
<p>这些命令应该有同一个父类Command。</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> Command {<br /><br /><span style="color: #0000ff">public</span>:<br /><br /><span style="color: #0000ff">virtual</span> ~Command() {}<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> execute() = 0;<span style="color: #008000">//执行该语句意味着执行命令</span><br /><br /><span style="color: #0000ff">protected</span>:<br /><br />Command() {}<br /><br />};<br /><br /></pre><br /></div>
<p>对于不能被取消（相对于取消操作来讲，后面再说），不需要参数的命令，使用C++模板来实现。因此ConcreteCommand的实现为：</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">template</span>&lt;<span style="color: #0000ff">class</span> Receiver&gt;<br /><br /><span style="color: #0000ff">class</span> SimpleCommand : <span style="color: #0000ff">public</span> Command {<br /><br /><span style="color: #0000ff">public</span>:<br /><br /><span style="color: #0000ff">typedef</span> <span style="color: #0000ff">void</span> (Receiver::*Action)();<br /><br />SimpleCommand(Receiver* r, Action a) :<br /><br />mReceiver(r), mAction(a)<br /><br />{<br /><br />}<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> execute() <br /><br />{<br /><br />(mReceiver-&gt;*mAction)();<br /><br />}<br /><br /><span style="color: #0000ff">private</span>:<br /><br />Action mAction;<br /><br />Receiver *mReceiver;<br /><br />};<br /><br /></pre><br /></div>
<p>Receiver即具体的接收者，也就是真正在执行命令的类，我们定义为DotaAI：</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> DotaAI {<br /><br /><span style="color: #0000ff">public</span>:<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> attackRoshan() <br /><br />{<br /><br />cout &lt;&lt; <span style="color: #006080">"Let's attack BOSS!"</span> &lt;&lt; endl;<br /><br />}<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> assemble()<br /><br />{<br /><br />cout &lt;&lt; <span style="color: #006080">"Assemble,warriors!"</span> &lt;&lt; endl;<br /><br />}<br /><br />};<br /><br /></pre><br /></div>
<p>调用类就很简单了，比如：</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> GamePlayer {<br /><br /><span style="color: #0000ff">public</span>:<br /><br /><span style="color: #0000ff">void</span> play()<br /><br />{<br /><br />DotaAI *vigoss = <span style="color: #0000ff">new</span> DotaAI;<br /><br />Command *c;<br /><br />c = <span style="color: #0000ff">new</span> SimpleCommand&lt;DotaAI&gt;(vigoss, &amp;DotaAI::attackRoshan);<br /><br />c-&gt;execute();<br /><br /><span style="color: #0000ff">delete</span> c;<br /><br />c = <span style="color: #0000ff">new</span> SimpleCommand&lt;DotaAI&gt;(vigoss, &amp;DotaAI::assemble);<br /><br />c-&gt;execute();<br /><br /><span style="color: #0000ff">delete</span> c;<br /><br /><span style="color: #0000ff">delete</span> vigoss;<br /><br />}<br /><br />};<br /></pre><br /></div>
<p>在main函数里new一个该类的对象，调用其play接口。 
<p>运行： 
<p>Let's attack BOSS! 
<p>Assemble,warriors! 
<p>没有问题。 
<p>这是比较简单的一种情形。Command有一个公共的接口，使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。 
<p>如果AI的接口已经给的非常全面，那么此时比如增加新的命令如DefenseCommand等，只需要增加新的command类即可。 
<p>或者如果当有新的AI的算法时，只要重新生成AI的指针来就可以了（只要新写一个继承自DotaAI的AI类，实现其attackRoshan，assemble虚函数即可）。 
<p>其实写到这里就有一个疑问了？ 
<p>我直接在Play里调用vigoss-&gt;attackRoshan(),vigoss-&gt;assemble()不是更简单，更直接么？ 
<p>主要考虑到这种情况： 
<p>有时必须想某对象提交请求，但并不知道关于被请求的操作或请求的接收者的任何信息。而Command类直到，它的execute（）接口会调用正确的类（Receiver）的方法。 
<p>相对于我们这个例子， 
<p>1. 如果命令除了AI外，比如命令动物信使而不是AI送东西的，同样可以用Command的子类实现，而对外则统一位Command 接口。 
<p>即将调用操作的对象与知道如何实现该操作的对象解耦。 
<p>2. 如果vigoss并不是在Play（）里new出来的，而Command有其指针的情况 
<p>此时Client里无法直接去调用DotaAI的方法。 
<p>3. 可以将多个命令复合为一个命令，比如说DotaAI还有其他一些方法：回城守护古树，去近卫打野怪，去买侦查守卫等，那么我可以这样去写：</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> AttackRoshanCommand : <span style="color: #0000ff">public</span> Command {<br /><br /><span style="color: #0000ff">public</span>:<br /><br />&#8230;<br /><br /><span style="color: #0000ff">void</span> execute()<br /><br />{ mAI-&gt;buyObserveGuard();<br /><br />mAI-&gt;assemble();<br /><br />mAI-&gt;attackRoshan();<br /><br />mAI-&gt;teleport();<br /><br />}<br /></pre><br /></div>
<p>也就是说对DotaAI给出的接口在ConcreteCommand里再次DIY一次，规划你的战术。或者像书里提到的MacroCommand类，用于管理一个子命令序列。 
<p>这也是实现Command模式时应当考虑的问题之一： 
<p>一个命令对象应该达到何种智能程度。 
<p>一个极端是它仅确定一个接收者和执行该请求的动作。另一极端是它自己实现所有功能，根本不需要额外的接收者对象。 
<p>书里还提到另外还有几个问题，有一个是支持取消和重做，即undo和redo。这需要Command提供方法逆转（reverse）它们操作的执行，首先修改Command类，增加一个virtual Unexecute()，当然不用是纯虚的，因为有的命令不用被取消。 
<p>考虑下dota里的神符，会使AI获得几种不同的状态（加速，隐身等等），于是我们在DotaAI里加上该属性和相对应的get，set方法。 
<p>同时写一个新的类:</p>
<div style="border-bottom: silver 1px solid; text-align: left; border-left: silver 1px solid; padding-bottom: 4px; line-height: 12pt; background-color: #f4f4f4; margin: 20px 0px 10px; padding-left: 4px; width: 97.5%; padding-right: 4px; font-family: 'Courier New', courier, monospace; direction: ltr; max-height: 200px; font-size: 8pt; overflow: auto; border-top: silver 1px solid; cursor: text; border-right: silver 1px solid; padding-top: 4px" id="codeSnippetWrapper"><pre style="border-bottom-style: none; text-align: left; padding-bottom: 0px; line-height: 12pt; border-right-style: none; background-color: #f4f4f4; margin: 0em; padding-left: 0px; width: 100%; padding-right: 0px; font-family: 'Courier New', courier, monospace; direction: ltr; border-top-style: none; color: black; font-size: 8pt; border-left-style: none; overflow: visible; padding-top: 0px" id="codeSnippet"><span style="color: #0000ff">class</span> ChangeStateCommand : <span style="color: #0000ff">public</span> Command {<br /><br /><span style="color: #0000ff">public</span>:<br /><br />ChangeStateCommand(DotaAI *ai, DotaAI::State s=DotaAI::INVALID) :<br /><br />mAI(ai),<br /><br />mState(s)<br /><br />{<br /><br />}<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> execute()<br /><br />{<br /><br /><span style="color: #0000ff">if</span> (mState==DotaAI::INVALID) {<br /><br />srand(time(NULL));<br /><br />mState = <span style="color: #0000ff">static_cast</span>&lt;DotaAI::State&gt;(rand()%6);<br /><br />}<br /><br />mLastState = mAI-&gt;getState();<br /><br />mAI-&gt;setState(mState);<br /><br />mAI-&gt;printState();<br /><br />}<br /><br /><span style="color: #0000ff">virtual</span> <span style="color: #0000ff">void</span> unExecute()<br /><br />{<br /><br />mAI-&gt;setState(mLastState);<br /><br />mAI-&gt;printState();<br /><br />}<br /><br /><span style="color: #0000ff">private</span>:<br /><br />DotaAI *mAI;<br /><br />DotaAI::State mState;<br /><br />DotaAI::State mLastState;<br /><br />};<br /><br /></pre><br /></div>
<p>DotaAI本身不会提供返回上一个状态的接口，不过我们可以在Command里实现（Command模式的优点）。 
<p>mLast用于记录上一状态。如果需要撤销多次，可能就需要一个列表或者栈之类的来实现了。但无论如何，按照Command设计模式，这些代码很自然的就完成了。而不用一直要去想每个类的关系，职责等等（当然在许多模式里选择Command模式的时候是要去思考的）。但一旦选定之后，代码就容易编写多了。 
<p style="background-color: red">附件为实例代码。 <a href="/Files/izualzhy/commandDesignPattern.rar">/Files/izualzhy/commandDesignPattern.rar</a></p> <img src ="http://www.cppblog.com/izualzhy/aggbug/160199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/izualzhy/" target="_blank">izualzhy</a> 2011-11-15 21:41 <a href="http://www.cppblog.com/izualzhy/archive/2011/11/15/160199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>