<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>army8735 &#187; 命令链</title>
	<atom:link href="http://army8735.org/tag/%e5%91%bd%e4%bb%a4%e9%93%be/feed" rel="self" type="application/rss+xml" />
	<link>http://army8735.org</link>
	<description>我可以A，我也可以-A，我可以同时A和-A。</description>
	<lastBuildDate>Fri, 25 Nov 2011 04:07:52 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>ecmascript中的命令链</title>
		<link>http://army8735.org/2010/01/25/567.html</link>
		<comments>http://army8735.org/2010/01/25/567.html#comments</comments>
		<pubDate>Mon, 25 Jan 2010 03:10:28 +0000</pubDate>
		<dc:creator>army8735</dc:creator>
				<category><![CDATA[前端开发]]></category>
		<category><![CDATA[设计模式]]></category>
		<category><![CDATA[命令]]></category>
		<category><![CDATA[命令链]]></category>

		<guid isPermaLink="false">http://army8735.org/?p=567</guid>
		<description><![CDATA[有人好奇JAse中的undo和redo是怎么做的，并且给出自己的设计做法也各有特色。我最初采用的是全文保存方法（这一方法也是最简单有效、使用最广泛的），后来被人痛批一顿换成了命令链。命令链其实是基于一种叫做“命令”的设计模式的，关键思想在于面向接口编程。这个东西随便搜搜有很多例子，我再重复制造一下轮子吧。
AS3中的命令链
as3对OOP的支持已经比较完善，所以可以据此写出很好的命令链，先从简单的例子来说起（以下代码均被简化）。假如我们要做一个Dog类，Dog的“说话”方式是bark（狗叫）：
class Dog {
	public function bark():void {
		trace("汪汪！");
	}
}
这很简单，没有什么特殊之处。可是奇怪的事情发生了，中学英语课本中有个闻名的澳洲野狗Dingo，它的叫声和普通的狗不一样：
class Dingo extends Dog {
	public override function bark():void {
		trace("呜呜——");
	}
}
也很简单，Dingo毕竟还是一只狗，只需继承并覆盖即可。好了现在需求来了：我们想听听这些动物的叫声是什么样的，只需要调用下相应的方法即可。
public class Test {
	public function Test():void {
		var...]]></description>
			<content:encoded><![CDATA[<p>有人好奇JAse中的undo和redo是怎么做的，并且给出自己的设计做法也各有特色。我最初采用的是全文保存方法（这一方法也是最简单有效、使用最广泛的），后来被人痛批一顿换成了命令链。命令链其实是基于一种叫做“命令”的设计模式的，关键思想在于<strong>面向接口编程</strong>。这个东西随便搜搜有很多例子，我再重复制造一下轮子吧。</p>
<h3>AS3中的命令链</h3>
<p>as3对OOP的支持已经比较完善，所以可以据此写出很好的命令链，先从简单的例子来说起（以下代码均被简化）。假如我们要做一个Dog类，Dog的“说话”方式是bark（狗叫）：</p>
<pre class="brush:as3">class Dog {
	public function bark():void {
		trace("汪汪！");
	}
}</pre>
<p>这很简单，没有什么特殊之处。可是奇怪的事情发生了，中学英语课本中有个闻名的澳洲野狗Dingo，它的叫声和普通的狗不一样：</p>
<pre class="brush:as3">class Dingo extends Dog {
	public override function bark():void {
		trace("呜呜——");
	}
}</pre>
<p>也很简单，Dingo毕竟还是一只狗，只需继承并覆盖即可。好了现在需求来了：我们想听听这些动物的叫声是什么样的，只需要调用下相应的方法即可。</p>
<pre class="brush:as3">public class Test {
	public function Test():void {
		var dog:Dog = new Dog();
		dog.bark();

		var dingo:Dog = new Dingo();
		dingo.bark();
	}
}</pre>
<p>目前为止一切顺利，可惜未来总是不像我们想象的那样。动物中又多了只公鸡，它的叫法完全不一样，是打鸣。</p>
<pre class="brush:as3">class Cock {
	public function crow():void {
		trace("喔喔——");
	}
}</pre>
<p>这下坏了，我们在听叫声的时候得记住，公鸡的叫法和狗不一样：</p>
<pre class="brush:as3">public class Test {
	public function Test():void {
		var dog:Dog = new Dog();
		dog.bark();

		var dingo:Dog = new Dingo();
		dingo.bark();

		var cock:Cock = new Cock();
		cock.crow();
	}
}</pre>
<p>倘若把这些动物排成一列，让它们依次各自叫一下的话就更麻烦了：</p>
<pre class="brush:as3">public class Test {
	public function Test():void {
		var list:Array = [new Dog(), new Dingo(), new Cock()];
		for (var i:int = 0; i &lt; list.length; i++) {
			var item = list[i];
			if (item is Dog) {
				item.bark();
			}
			else if (item is Cock) {
				item.crow();
			}
		}
	}
}</pre>
<p>我们得对每种类型做单独判断，可所谓不胜其烦，动物的种类是不可预知的，倘若再来一只猫，还要继续增加判别吗？当然不能。</p>
<p>显然，关于叫法这里我们需要解耦。不管你是什么动物，这些叫声其实都可以概括为“说话”（动物们也有自己的语言和说话方式）。在主调程序中，我们并不想关心动物是“怎么说话”、“说些什么”的，我们只想调用一个命令，动物对象就能自动按照自己的方式来“说话”，甚至我们不用关心这个动物到底是什么。通过分离做什么和怎么做来实现这个目标的方式，就称为<strong>命令模式</strong>。</p>
<p>好了，首先是命令接口：</p>
<pre class="brush:as3">public interface Command {
	function say():void;
}</pre>
<p>所有动物只要实现了这个接口就行，各自具体的执行方式自己来定：</p>
<pre class="brush:as3">class Dog implements Commad {
	public function say():void {
		bark();
	}
	protected function bark():void {
		trace("汪汪！");
	}
}

class Dingo extends Dog {
	protected override function bark():void {
		trace("呜呜——"");
	}
}

class Cock implements Commad {
	public function say():void {
		crow();
	}
	private function crow():void {
		trace("喔喔——");
	}
}</pre>
<p>这样的话，在主调程序中，管它排成一列的动物有哪些，统统视作Command接口的实现即可：</p>
<pre class="brush:as3">public class Test {
	public function Test():void {
		var list:Array = [new Dog(), new Dingo(), new Cock()];
		for (var i:int = 0; i &lt; list.length; i++) {
			(list[i] as Command).say();
		}
	}
}</pre>
<p>哪怕再多出来一只猫，我们只需要增加猫的这一类别，列表中多出只猫来即可，主调程序无需关心具体实现。甚至猫的方式更加复杂有感情：</p>
<pre class="brush:as3">class Cat implements Command {
	private var isHappy:Boolean = true;

	public function say():void {
		if (isHappy) {
			purr();
		}
		else {
			mew();
		}
	}

	private function purr():void {
		trace("咕噜……");
	}
	private function mew():void {
		trace("喵——");
	}
}

public class Test {
	public function Test():void {
		var list:Array = [new Dog(), new Dingo(), new Cock(), new Cat()];
		for (var i:int = 0; i &lt; list.length; i++) {
			(list[i] as Command).say();
		}
	}
}</pre>
<h3>undo、redo的命令链</h3>
<p>熟悉了这些后，undo和redo的做法同理：只需实现了命令接口，每个操作各是独自的命令，互不干涉，具体实现自己决定，最大程度上解耦。</p>
<p>定义命令接口（以下代码均被简化）：</p>
<pre class="brush:as3">package command {

	public interface ICommand {
		function redo():void;
		function undo():void;
	}

}</pre>
<p>输入命令——也就是在编辑器中敲入代码时：</p>
<pre class="brush:as3">package command {
	import flash.text.*;

	public class InputCommand implements ICommand {
		private var tf:TextField;
		private var index:int;
		private var text:String;

		public function InputCommand(tf:TextField, index:int, text:String):void {
			this.tf = tf;
			this.index = index;
			this.text = text;
		}

		public function redo():void {
			tf.setSelection(index + text.length, index + text.length);
		}
		public function undo():void {
			tf.replaceText(index, index + text.length, "");
		}
	}
}</pre>
<p>删除命令——当按Delete键将编辑器里的内容删除时：</p>
<pre class="brush:as3">package command {
	import flash.text.*;

	public class DeleteCommand implements ICommand {
		private var tf:TextField;
		private var index:int;
		private var end:int;
		private var text:String;

		public function DeleteCommand(tf:TextField, index:int, end:int, text:String):void {
			this.tf = tf;
			this.index = index;
			this.end = end;
			this.text = text;
		}

		public function redo():void {
			tf.replaceText(index, index + text.length, "");
		}
		public function undo():void {
			tf.replaceText(index, index, text);
		}
	}
}</pre>
<p>于是，每当我们输入一个字符时，向一个记录命令步骤的数组里存入一个输入命令；而在按下Delete键时，存入一个删除命令；其它多一个命令多建一个类实现，这样一个链连下来就是<strong>命令链</strong>。</p>
<pre class="brush:as3">package command {
	import flash.text.*;
	import edit.*;

	public class CommandList {
		private var undoList:Array, redoList:Array;

		public function CommandList():void {
			clear();
		}

		public function addCommand(cmd:ICommand):void {
			//超过最大命令链长度需先出队列一个
			if (undoList.length &gt; Editor.UNDO_SIZE) {
				undoList.shift();
			}
			//每添加一次命令，清空redoList
			if(redoList.length) {
				redoList = new Array();
			}
			undoList.push(cmd);
		}
		public function undo():Boolean {
			//undoList中有命令则执行，并将相应命令出栈存入redoList中
			if(undoList.length) {
				var cmd:ICommand = undoList.pop() as ICommand;
				cmd.undo();
				redoList.push(cmd);
				return true;
			}
			//为空返回false
			else {
				return false;
			}
		}
		public function redo():Boolean {
			//redoList中有命令则执行，并将相应命令出栈存入undoList中
			if (redoList.length) {
				var cmd:ICommand = redoList.pop() as ICommand;
				cmd.redo();
				undoList.push(cmd);
				return true;
			}
			//为空返回false
			else {
				return false;
			}
		}
		public function clear():void {
			undoList = new Array();
			redoList = new Array();
		}
	}

}</pre>
<p>其实基于fp10中as3的新特性，有个叫Vector的类，它是存储命令链数组的更好的替代者，因为它规定数组里所有元素的类型必须是相同的。这样在编译期间便能防止出错，于是undoList和redoList更好的定义方式是这样：</p>
<pre class="brush:as3">undoList = new Vector.&lt;ICommand&gt;();
redoList = new Vector.&lt;ICommand&gt;();</pre>
<p>熟悉Java的很容易理解：这就是Java5+中的泛型。</p>
<h3>js中的命令链</h3>
<p>扯了这么远才到js，而且篇幅也不会多长，真是愧对这标题。</p>
<p>js中并不存在接口，所以也就无从说起面向接口编程。然而以此就下定论说“不能使用命令链”尚为时过早。的确，js由于其本身特性原因，不能像传统OOP语言那样使用，但命令链模式其实就存在于日常生活当中：</p>
<pre class="brush:js">function Dog() {
}
Dog.prototype.say = function() {
	this.bark();
}
Dog.prototype.bark = function() {
	alert("汪汪！");
}

function Dingo() {
}
Dingo.prototype = new Dog();
Dingo.prototype.bark = function() {
	alert("呜呜——");
}

function Cock() {
}
Cock.prototype.say = function() {
	this.crow();
}
Cock.prototype.crow = function() {
	alert("喔喔——");
}

function Cat() {
	this.bHappy = true;
}
Cat.prototype.say = function() {
	if(this.bHappy) {
		this.purr();
	}
	else {
		this.mew();
	}
}
Cat.prototype.purr = function() {
	alert("咕噜……");
}
Cat.prototype.mew = function() {
	alert("喵——");
}

var aList = [new Dog(), new Dingo(), new Cock(), new Cat()];
for(var i = 0; i &lt; aList.length; i++) {
	aList[i].say();
}</pre>
<p>基于弱类型，每个类都可以拥有一个同名方法，广义上说这也可以看作是实现了一个“通用接口”。js没有编译过程，因此无法在前期（编译期）实现检查，只能靠后期（运行时）来确定。网上也有很多例子模拟出js的接口和命令链，使得一个类在实现接口但却没有实现接口定义的方法时抛出异常。这样做的好处是在运行出错时有详尽的异常信息供使用者检查，但在目前firebug等工具的情况下所提供的能效有限，在大型的开发中才会有很好的帮助。因此我们目前大部分情况下——似乎最好的办法就是<strong>相信别人</strong>。</p>
]]></content:encoded>
			<wfw:commentRss>http://army8735.org/2010/01/25/567.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

