javascript设计模式--命令模式

   1 <!DOCTYPE html>
   2 <html>
   3 <head>
   4     <title>命令模式</title>
   5     <meta charset="utf-8">
   6 </head>
   7 <body>
   8 
   9 <script>
  10 /**
  11  * 命令模式
  12  *
  13  * 定义:
  14  * 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
  15  *
  16  * 本质:
  17  * 封装请求
  18  *
  19  * 命令模式是一种封装方法调用的方式。命令模式与普通函数所有不同。它可以用来对方法调用进行参数化处理和传送,经这样处理过的方法调用可以在任何需要的时候执行。它也可以用来消除调用操作的对象和实现操作对象之间的耦合,这位各种具体的类的更换带来了极大的灵活性。这种模式可以用在许多不同场合,不过它在创建用户界面这一方面非常有用,特别是在需要不受限的(unlimited)取消(undo)操作的时候,它还可以用来替代回调函数,因为它能够提高在对象之间传递的操作的模块化程度。
  20  *
  21  * 在命令模式中,会定义一个命令的接口,用来约束所有的命令对象,然后提供具体的命令实现,每个命令实现对象是对客户端某个请求的封装,对应于机箱上的按钮,一个机箱上可以有很多按钮,也就相当于会有多个具体的命令实现对象。
  22  * 在命令模式中,命令对象并不知道如何处理命令,会有相应的接收者对象来真正执行命令。就像电脑的例子,机箱上的按钮并不知道如何处理功能,而是把这个请求转发给主板,由主办来执行真正的功能,这个主板就相当于命令模式的接收者。
  23  * 在命令模式中,命令对象和接收者对象的关系,并不是与生俱来的,需要有一个装配的过程,命令模式中的Client对象可以实现这样的功能。这就相当于在电脑的例子中,有了机箱上的按钮,也有了主板,还需要一个连接线把这个按钮连接到主板上才行。
  24  * 命令模式还会提供一个Invoker对象来持有命令对象。就像电脑的例子,机箱上会有多个按钮,这个机箱就相当于命令模式的Invoker对象。这样一来,命令模式的客户端就可以通过Invoker来触发并要求执行相应的命令了,这也相当于真正的客户是按下机箱上的按钮来操作电脑一样。
  25  *
  26  * 命令模式的关键
  27  * 命令模式的关键之处就是把请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,整个命令模式都是围绕这个对象在进行。
  28  *
  29  * 命令模式的组装和调用
  30  * 在命令模式中经常会有一个命令的组装者,用它来维护命令的“虚”实现和真实实现之间的关系。如果是超级智能的命令,也就是说命令对象自己完全实现好了,不需要接收者,那就是命令模式的退化,不需要接受者,自然也不需要组装者了。
  31  * 而真正的用户就是具体化请求的内容,然后提交请求进行触发就可以了。真正的用户会通过Invoker来触发命令。
  32  * 在实际开发过程中,Client和Invoker可以融合在一起,由客户在使用命令模式的时候,先进行命令对象和接收者的组装,组装完成后,就可以调用命令执行请求。
  33  *
  34  * 命令模式的接收者
  35  * 接收者可以是任意的类,对它没有什么特殊要求,这个对象知道如何真正执行命令的操作,执行时是从Command的实现类里面转调过来。
  36  * 一个接收者对象可以处理多个命令,接收者和命令之间没有约定的对应关系。接收者提供的方法个数,名称,功能和命令中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了。
  37  *
  38  * 命令模式的调用顺序
  39  * 使用命令模式的过程分成两个阶段,一个阶段是组装对象和接收者对象的过程,另外一个阶段是触发调用Invoker,来让命令真正的执行。
  40  * 组装过程:
  41  * 1.创建接收者对象。
  42  * 2.创建命令对象,设置命令对象和接收者对象的关系。
  43  * 3.创建Invoker对象。
  44  * 4.把命令对象设置到Invoker中,让Invoker持有命令对象。
  45  * 执行过程:
  46  * 1.调用Invoker的方法,触发要求执行命令。
  47  * 2.要求持有的命令对象只能执行功能。
  48  * 3.要求持有的接收者真正实现功能。
  49  *
  50  */
  51 
  52 (function () {
  53     // 示例代码
  54 
  55     /**
  56      * 具体的命令实现对象
  57      * @params {Object} receiver 持有相应的接收者对象
  58      */
  59     function Command(receiver) {
  60         this.receiver = receiver;
  61         // 命令对象可以有自己的状态
  62         this.state = '';
  63     }
  64 
  65     Command.prototype.execute = function () {
  66         // 通常会转调接收者对象的相应方法,让接收者来真正执行功能
  67         this.receiver.action();
  68     };
  69 
  70     // 接收者对象
  71     function Receiver() {
  72     }
  73 
  74     // 真正执行命令相应地操作
  75     Receiver.prototype.action = function () {
  76     };
  77 
  78     /**
  79      * 调用者
  80      *
  81      */
  82     function Invoker() {
  83     }
  84 
  85     /**
  86      * @params {Object} command 持有命令对象
  87      */
  88     Invoker.prototype.setCommand = function (command) {
  89         this.command = command;
  90     };
  91     // 要求命令执行请求
  92     Invoker.prototype.runCommand = function () {
  93         this.command.execute();
  94     };
  95 
  96     new function Client() {
  97         var receiver = new Receiver();
  98         var command = new Command(receiver);
  99         var invoker = new Invoker();
 100         invoker.setCommand(command);
 101         invoker.runCommand();
 102     }();
 103 }());
 104 
 105 /*
 106  命令的结构
 107 
 108  最简形式的命令对象是一个操作和用以调用这个操作的对象的结合体。所有的命令对象都有一个执行操作(execute operation),其用途就是调用命令对象所绑定的操作。在大多数命令对象中,这个操作是一个名为execute或run的方法。使用同样接口的所有命令对象都可以被同等对待,并且可以随意互换,这是命令模式的魅力之一。
 109 
 110  假设你想设计一个网页,客户可以在上面执行一些与自己的账户相关的操作,比如启用和停用某些广告。因为不知道其中的具体广告数量,所以你想设计一个尽可能灵活的用户界面(UI)。为此你打算用命令模式来弱化按钮之类的用户界面元素与其操作之间的耦合。
 111  */
 112 
 113 // 定义两个类,分别用来封装广告的start方法和stop方法
 114 // StopAd command class
 115 var StopAd = function (adObject) {
 116     this.ad = adObject;
 117 };
 118 StopAd.prototype.execute = function () {
 119     this.ad.stop();
 120 };
 121 
 122 // StartAd command class
 123 var StartAd = function (adObject) {
 124     this.ad = adObject;
 125 };
 126 StartAd.prototype.execute = function () {
 127     this.ad.start();
 128 };
 129 /*
 130  现在有个两个可用在用户界面中的类,它们具有相同的接口。你不知道也不关心adObject的具体实现细节,只要它实现了start和stop方法就行。借助于命令模式,可以实现用户界面对象与广告对象的隔离。
 131  */
 132 
 133 /*
 134  下面的代码创建的用户界面中,用户名下的每个广告都有两个按钮,分别用于启动和停止广告的轮播:
 135  */
 136 // implementation code
 137 var ads = getAds();
 138 for (var i = 0, len = ads.length; i < len; i++) {
 139     // Create command objects for starting and stopping the ad
 140     var startCommand = new StartAd(ads[i]);
 141     var stopCommand = new StopAd(ads[i]);
 142 
 143     // Create the UI elements that will execute the command on click
 144     new UIButton('Start ' + ads[i].name, startCommand);
 145     new UIButton('stop ' + ads[i].name, stopCommand);
 146 }
 147 /*
 148  UIButton类的构造函数有两个参数,一个是按钮上的文字,另一个是命令对象。它会在网页上生成一个按钮,该按钮被点击时会执行那个命令对象的execute方法。这个类也不需要知道所用命令对象的确切实现。因为所有命令对象都实现了execute方法,所以可以把任何一种命令对象提供给UIButton。这有助于创建高度模块化和低耦合的用户界面。
 149  */
 150 
 151 /*
 152  用闭包创建命令对象
 153 
 154  还有另外一种办法可以用来封装函数。这种办法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。如果想要创建的命令对象像前例中那样只有一个方法。那么这种办法由其方便。现在你不再调用execute方法,因为那个命令可以作为函数直接执行。这样做还可以省却作用域和this关键字的绑定的烦恼。
 155  */
 156 
 157 // Command using closures
 158 function makeSart(adObject) {
 159     return function () {
 160         adObject.start();
 161     };
 162 }
 163 function makeStop(adObject) {
 164     return function () {
 165         adObject.stop();
 166     };
 167 }
 168 
 169 // Implementation code
 170 var startCommand = makeStart(ads[0]);
 171 var stopCommand = makeStop(ads[0]);
 172 
 173 startCommand();
 174 stopCommand();
 175 /*
 176  不适用于需要多个命令方法的场合,比如后面要实现取消功能的示例
 177  */
 178 
 179 /*
 180  客户,调用者和接收者
 181 
 182  这个系统中有三个参与者:客户(client),调用者(invoking object)和接收者(receiving object)。客户负责实例化命令并将其交给调用者。在前面的例子中,for循环中的代码就是客户。它通常被包装为一个对象,但也不是非这样不可。调用者接过命令并将其保存下来。它会在某个时候调用该命令对象的execute方法,或者将其交给另一个潜在的调用者。前例中的调用者就是UIButton类创建的按钮。用户点击它的时候,它就会调用命令对象的execute方法。接收者则是实际执行操作的对象。调用者进行“commandObject.execute()”这种形式的调用时,它所调用的方法将转而以“receiver.action()”这种形式调用恰当的方法。而接收者就是广告对象,它所能执行的操作要么是start方法,要么是stop方法。
 183 
 184  客户创建命令,调用者执行该命令,接收者在命令执行时执行相应操作。
 185  所有使用命令模式的系统都有客户和调用者,但不一定有接收者。
 186  */
 187 
 188 // 在命令模式中使用接口
 189 // If no exception is thrown, youcan safely invoke the
 190 // execute operation
 191 someCommand.execute();
 192 
 193 // 如果用必报来创建命令函数,只需检查是否为函数即可
 194 if (typeof someCommand !== 'function') {
 195     throw new Error('Command isn\'t a function');
 196 }
 197 
 198 
 199 // 命令对象的类型
 200 /*
 201  简单命令对象就是把现有接收者的操作(广告对象的start和stop方法)与调用者(按钮)绑定在一起。这类命令对象最简单,其模块程度也最高。它们与客户,接收者和调用者之间只是松散地偶合在一起:
 202  */
 203 // SimpleCommand, a loosely coupled, simple command class.
 204 var SimpleCommand = function (receiver) {
 205     this.receiver = receiver;
 206 };
 207 SimpleCommand.prototype.execute = function () {
 208     this.receiver.action();
 209 };
 210 
 211 /*
 212  另一种则是那种封装着一套复杂指令的命令对象。这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。它并不把操作委托给接收者实现,所有用于实现相关操作的代码都包含在其内部:
 213  */
 214 // ComplexCommand, a tightly coupled, complex command class.
 215 var ComplexCommand = function () {
 216     this.logger = new Logger();
 217     this.xhrHandler = XhrManager.createXhrHandler();
 218     this.parameters = {};
 219 };
 220 ComplexCommand.prototype = {
 221     setParameter: function (key, value) {
 222         this.parameters[key] = value;
 223     },
 224     execute: function () {
 225         this.logger.log('Executing command');
 226         var postArray = [];
 227         for (var key in this.parameters) {
 228             if (this.parameters.hasOwnProperty(key)) {
 229                 postArray.push(key + '=' + this.parameters[key]);
 230             }
 231         }
 232         var postString = postArray.join('&');
 233         this.xhrHandler.request(
 234             'POST',
 235             'script.php',
 236             function () {
 237             },
 238             postString
 239         );
 240     }
 241 };
 242 
 243 /*
 244  有些命令对象不但封装了接收者的操作,而且其execute方法中也具有一些实现代码。这类命令对象是一个灰色地带:
 245  */
 246 // GreyAreaCommand, somewhere between simple and complex
 247 var GreyAreaCommand = function (receiver) {
 248     this.logger = new Logger();
 249     this.receiver = receiver;
 250 };
 251 GreyAreaCommand.prototype.execute = function () {
 252     this.logger.log('Executing command');
 253     this.receiver.prepareAction();
 254     this.receiver.action();
 255 };
 256 
 257 /*
 258  简单命令对象一般用来消除两个对象(接受着和调用者)之间的耦合,而复杂命令对象则一般用来封装不可分的或事务性的指令。
 259  */
 260 
 261 // 实例: 菜单项
 262 // 菜单组合对象
 263 /*
 264  接下来要实现的事Menubar,Menu和MenuItem类,作为一个整体,他们要能显示所有可用操作,并且根据要求调用这些操作,Menubar和Menu都是组合对象类,而MenuItem则是叶类。Menubar类保存着所有Menu实例:
 265  */
 266 // MenuBar class, a composite
 267 var MenuBar = function () {
 268     this.menus = {};
 269     this.element = document.createElement('ul');
 270     this.element.style.display = 'none';
 271 };
 272 MenuBar.prototype = {
 273     add: function (menuObject) {
 274         this.menus[menuObject.name] = menuObject;
 275         this.element.appendChild(this.menus[menuObject.name].getElement());
 276     },
 277     remove: function (name) {
 278         delete this.menus[name];
 279     },
 280     getChild: function (name) {
 281         return this.menus[name];
 282     },
 283     getElement: function () {
 284         return this.element;
 285     },
 286     show: function () {
 287         this.element.style.display = '';
 288         for (var name in this.menus) {
 289             this.menus[name].show();
 290         }
 291     }
 292 };
 293 
 294 // Menu class, a composite
 295 var Menu = function (name) {
 296     this.name = name;
 297     this.items = {};
 298     this.element = document.createElement('li');
 299     this.element.style.display = 'none';
 300     this.container = document.createElement('ul');
 301     this.element.appendChild(this.container);
 302 };
 303 Menu.prototype = {
 304     add: function (menuItemObject) {
 305         this.items[menuItemObject.name] = menuItemObject;
 306         this.container.appendChild(this.items[menuItemObject.name].getElement());
 307     },
 308     remove: function () {
 309         delete this.items[name];
 310     },
 311     getChild: function (name) {
 312         return this.items[name];
 313     },
 314     getElement: function () {
 315         return this.element;
 316     },
 317     show: function () {
 318         this.element.style.display = '';
 319         for (var name in this.items) {
 320             this.items[name].show();
 321         }
 322     }
 323 };
 324 
 325 // 调用者类
 326 // MenuItem class, a leaf
 327 var MenuItem = function (name, command) {
 328     this.name = name;
 329     this.element = document.createElement('li');
 330     this.element.style.display = 'none';
 331     this.anchor = document.createElement('a');
 332     this.anchor.href = '#';
 333     this.element.appendChild(this.anchor);
 334     this.anchor.innerHTML = this.name;
 335 
 336     addEvent(this.anchor, 'click', function (e) {
 337         e = e || window.event;
 338         if (typeof e.preventDefault === 'function') {
 339             e.preventDefault();
 340         } else {
 341             e.returnValue = false;
 342         }
 343         command.execute();
 344     });
 345 };
 346 MenuItem.prototype = {
 347     add: function () {
 348     },
 349     remove: function () {
 350     },
 351     getChild: function () {
 352     },
 353     getElement: function () {
 354         return this.element;
 355     },
 356     show: function () {
 357         this.element.style.display = '';
 358     }
 359 };
 360 
 361 // 命令类
 362 // MenuCommand class, a command object
 363 var MenuCommand = function (action) {
 364     this.action = action;
 365 };
 366 MenuCommand.prototype.execute = function () {
 367     this.action.action();
 368 };
 369 
 370 
 371 // Receiver objects, instantiated from existing classes
 372 var Test1 = function () {
 373     console.log('test1');
 374 };
 375 Test1.prototype = {
 376     action: function () {
 377         console.log('this is test1 fn1');
 378     }
 379 };
 380 var Test2 = function () {
 381     console.log('test2');
 382 };
 383 Test2.prototype = {
 384     action: function () {
 385         console.log('this is test2 fn1');
 386     }
 387 };
 388 var Test3 = function () {
 389     console.log('test3');
 390 };
 391 var test1 = new Test1();
 392 var test2 = new Test2();
 393 var test3 = new Test3();
 394 
 395 // Create the menu bar
 396 var appMenuBar = new MenuBar();
 397 
 398 // The File menu
 399 var fileMenu = new Menu('File');
 400 
 401 var test1Command1 = new MenuCommand(test1);
 402 
 403 fileMenu.add(new MenuItem('test1-1', test1Command1));
 404 
 405 appMenuBar.add(fileMenu);
 406 
 407 var insertMenu = new Menu('Insert');
 408 var test2Command2 = new MenuCommand(test2);
 409 insertMenu.add(new MenuItem('test2-1', test2Command2));
 410 
 411 appMenuBar.add(insertMenu);
 412 
 413 document.body.appendChild(appMenuBar.getElement());
 414 appMenuBar.show();
 415 
 416 
 417 (function () {
 418     // 补偿式或者反操作式
 419 
 420     // 取消操作和命令日志
 421 
 422     // ReversibleCommand interface
 423     var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']);
 424 
 425     // 接下来要做的是创建4个命令类,
 426     // 它们分别用来向上下左右四个方向移动指针:
 427     var MoveUp = function (cursor) {
 428         this.cursor = cursor;
 429     };
 430     MoveUp.prototype = {
 431         execute: function () {
 432             this.cursor.move(0, -10);
 433         },
 434         undo: function () {
 435             this.cursor.move(0, 10);
 436         }
 437     };
 438 
 439     var MoveDown = function (cursor) {
 440         this.cursor = cursor;
 441     };
 442     MoveDown.prototype = {
 443         execute: function () {
 444             this.cursor.move(0, 10);
 445         },
 446         undo: function () {
 447             this.cursor.move(0, -10);
 448         }
 449     };
 450 
 451     var MoveLeft = function (cursor) {
 452         this.cursor = cursor;
 453     };
 454     MoveLeft.prototype = {
 455         execute: function () {
 456             this.cursor.move(-10, 0);
 457         },
 458         undo: function () {
 459             this.cursor.move(10, 0);
 460         }
 461     };
 462 
 463     var MoveRight = function (cursor) {
 464         this.cursor = cursor;
 465     };
 466     MoveRight.prototype = {
 467         execute: function () {
 468             this.cursor.move(10, 0);
 469         },
 470         undo: function () {
 471             this.cursor.move(-10, 0);
 472         }
 473     };
 474 
 475     // 接收者,负责实现指针移动
 476     // Cursor class 实现了命令类所要求的操作
 477     var Cursor = function (width, height, parent) {
 478         this.width = width;
 479         this.height = height;
 480         this.position = {
 481             x: width / 2,
 482             y: height / 2
 483         };
 484 
 485         this.canvas = document.createElement('canvas');
 486         this.canvas.width = this.width;
 487         this.canvas.height = this.height;
 488         parent.appendChild(this.canvas);
 489 
 490         this.ctx = this.canvas.getContext('2d');
 491         this.ctx.fillStyle = '#cc0000';
 492         this.move(0, 0);
 493     };
 494     Cursor.prototype.move = function (x, y) {
 495         this.position.x += x;
 496         this.position.y += y;
 497 
 498         this.ctx.clearRect(0, 0, this.width, this.height);
 499         this.ctx.fillRect(this.position.x, this.position.y, 3, 3);
 500     };
 501 
 502     // 下面这个装饰者的作用就是在执行一个命令之前先将其压栈
 503     // UndoDecorator class
 504     var UndoDecorator = function (command, undoStack) {
 505         this.command = command;
 506         this.undoStack = undoStack;
 507     };
 508     UndoDecorator.prototype = {
 509         execute: function () {
 510             this.undoStack.push(this.command);
 511             this.command.execute();
 512         },
 513         undo: function () {
 514             this.command.undo();
 515         }
 516     };
 517 
 518     // 用户界面类,负责生成必要的HTML元素,并且为其注册click事件监听器,
 519     // 这些监听器要么调用execute方法要么调用undo方法:
 520     // CommandButton class
 521     var CommandButton = function (label, command, parent) {
 522         this.element = document.createElement('button');
 523         this.element.innerHTML = label;
 524         parent.appendChild(this.element);
 525 
 526         addEvent(this.element, 'click', function () {
 527             command.execute();
 528         });
 529     };
 530 
 531     // UndoButton class
 532     var UndoButton = function (label, parent, undoStack) {
 533         this.element = document.createElement('button');
 534         this.element.innerHTML = label;
 535         parent.appendChild(this.element);
 536 
 537         addEvent(this.element, 'click', function () {
 538             if (undoStack.length === 0) return;
 539             var lastCommand = undoStack.pop();
 540             lastCommand.undo();
 541         });
 542     };
 543     /*
 544      像UndoDecorator类一样,UndoButton类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。
 545      */
 546 
 547     // Implementation code
 548     var body = document.body;
 549     var cursor = new Cursor(400, 400, body);
 550     var undoStack = [];
 551 
 552     var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack);
 553     var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack);
 554     var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack);
 555     var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack);
 556 
 557     var upButton = new CommandButton('Up', upCommand, body);
 558     var downButton = new CommandButton('Down', downCommand, body);
 559     var leftButton = new CommandButton('Left', leftCommand, body);
 560     var rightButton = new CommandButton('Right', rightCommand, body);
 561     var undoButton = new UndoButton('Undo', body, undoStack);
 562 }());
 563 
 564 
 565 (function () {
 566     // 使用命令日志实现不可逆操作的取消
 567     /*
 568      在画布上画线很容易,不过要取消这条线的绘制是不可能的。从一个点到另一个点的移动这种操作具有精确的对立操作,执行后者的结果看起来就像前者被逆转了一样。但是对于从A到B画一条线这种操作,从B到A再画一条线是无法逆转前一操作的,这只不过是在第一条线的上方又画一条线而已。
 569 
 570      取消这种操作的唯一办法是清除状态,然后把之前执行过的操作(不含最近那个)一次重做一遍。这很容易办到,为此需要把所有执行过的命令记录在栈中。要想取消一个操作,需要做的就是从栈中弹出最近那个命令并弃之不用,然后清理画布并从头开始重新执行记录下来的所有命令。
 571      */
 572 
 573     // Movement commands
 574     var MoveUp = function (cursor) {
 575         this.cursor = cursor;
 576     };
 577     MoveUp.prototype = {
 578         execute: function () {
 579             this.cursor.move(0, -10);
 580         }
 581     };
 582 
 583     var MoveDown = function (cursor) {
 584         this.cursor = cursor;
 585     };
 586     MoveDown.prototype = {
 587         execute: function () {
 588             this.cursor.move(0, 10);
 589         }
 590     };
 591 
 592     var MoveLeft = function (cursor) {
 593         this.cursor = cursor;
 594     };
 595     MoveLeft.prototype = {
 596         execute: function () {
 597             this.cursor.move(-10, 0);
 598         }
 599     };
 600 
 601     var MoveRight = function (cursor) {
 602         this.cursor = cursor;
 603     };
 604     MoveRight.prototype = {
 605         execute: function () {
 606             this.cursor.move(10, 0);
 607         }
 608     };
 609 
 610     // Cursor class, with an internal command stack
 611     var Cursor = function (width, height, parent) {
 612         this.width = width;
 613         this.height = height;
 614         this.commandStack = [];
 615 
 616         this.canvas = document.createElement('canvas');
 617         this.canvas.width = this.width;
 618         this.canvas.height = this.height;
 619         parent.appendChild(this.canvas);
 620 
 621         this.ctx = this.canvas.getContext('2d');
 622         this.ctx.strokeStyle = '#cc0000';
 623         this.move(0, 0);
 624     };
 625     Cursor.prototype = {
 626         move: function (x, y) {
 627             var that = this;
 628             this.commandStack.push(function () {
 629                 that.lineTo(x, y);
 630             });
 631             this.executeCommands();
 632         },
 633         lineTo: function (x, y) {
 634             this.position.x += x;
 635             this.position.y += y;
 636             this.ctx.lineTo(this.position.x, this.position.y);
 637         },
 638         executeCommands: function () {
 639             this.position = {
 640                 x: this.width / 2,
 641                 y: this.height / 2
 642             };
 643             this.ctx.clearRect(0, 0, this.width, this.height);
 644             this.ctx.beginPath();
 645             this.ctx.moveTo(this.position.x, this.position.y);
 646             for (var i = 0, len = this.commandStack.length; i < len; i++) {
 647                 this.commandStack[i]();
 648             }
 649             this.ctx.stroke();
 650         },
 651         undo: function () {
 652             this.commandStack.pop();
 653             this.executeCommands();
 654         }
 655     };
 656 
 657     // UndoButton class
 658     var UndoButton = function (label, parent, cursor) {
 659         this.element = document.createElement('button');
 660         this.element.innerHTML = label;
 661         parent.appendChild(this.element);
 662         addEvent(this.element, 'click', function () {
 663             cursor.undo();
 664         });
 665     };
 666     // CommandButton class
 667     var CommandButton = function (label, command, parent) {
 668         this.element = document.createElement('button');
 669         this.element.innerHTML = label;
 670         parent.appendChild(this.element);
 671 
 672         addEvent(this.element, 'click', function () {
 673             command.execute();
 674         });
 675     };
 676 
 677     var body = document.body;
 678     var cursor = new Cursor(400, 400, body);
 679 
 680     var upCommand = new MoveUp(cursor);
 681     var downCommand = new MoveDown(cursor);
 682     var leftCommand = new MoveLeft(cursor);
 683     var rightCommand = new MoveRight(cursor);
 684 
 685     var upButton = new CommandButton('Up', upCommand, body);
 686     var downButton = new CommandButton('Down', downCommand, body);
 687     var leftButton = new CommandButton('Left', leftCommand, body);
 688     var rightButton = new CommandButton('Right', rightCommand, body);
 689     var undoButton = new UndoButton('Undo', body, cursor);
 690 }());
 691 
 692 (function () {
 693     // 宏命令
 694     /*
 695      去饭店吃饭过程。
 696 
 697      客户: 只负责发出命令,就是点菜操作。
 698      命令对象: 就是点的菜。
 699      服务员: 知道真正的接收者是谁,同时持有菜单,当你点菜完毕,服务员就启动命令执行。
 700      后厨, 凉菜部: 相当于接收者。
 701 
 702      菜单命令包含多个命令对象
 703      */
 704 
 705     // 坐热菜的厨师
 706     var HotCook = function () {
 707     };
 708     HotCook.prototype = {
 709         cook: function (name) {
 710             console.log('本厨师正在做:' + name);
 711         }
 712     };
 713 
 714     // 做凉菜的厨师
 715     var CoolCook = function () {
 716     };
 717     CoolCook.prototype = {
 718         cook: function (name) {
 719             console.log('凉菜' + name + '已经做好,本厨师正在装盘。');
 720         }
 721     }
 722 
 723     // 定义了三道菜,每道菜是一个命令对象
 724 
 725     var DuckCommand = function () {
 726         this.cookApi = null;
 727     };
 728     DuckCommand.prototype = {
 729         constructor: DuckCommand,
 730         setCookApi: function (cookApi) {
 731             this.cookApi = cookApi;
 732         },
 733         execute: function () {
 734             this.cookApi.cook('北京烤鸭');
 735         }
 736     };
 737 
 738     var ChopCommand = function () {
 739         this.cookApi = null;
 740     };
 741     ChopCommand.prototype = {
 742         constructor: ChopCommand,
 743         setCookApi: function (cookApi) {
 744             this.cookApi = cookApi;
 745         },
 746         execute: function () {
 747             this.cookApi.cook('绿豆排骨煲');
 748         }
 749     };
 750 
 751     var PorkCommand = function () {
 752         this.cookApi = null;
 753     };
 754     PorkCommand.prototype = {
 755         constructor: PorkCommand,
 756         setCookApi: function (cookApi) {
 757             this.cookApi = cookApi;
 758         },
 759         execute: function () {
 760             this.cookApi.cook('蒜泥白肉');
 761         }
 762     };
 763 
 764     // 菜单对象,宏命令对象
 765     var MenuCommand = function () {
 766         var col = [];
 767 
 768         this.addCommand = function (cmd) {
 769             col.push(cmd);
 770         };
 771 
 772         this.execute = function () {
 773             for (var i = 0 , len = col.length; i < len; i++) {
 774                 col[i].execute();
 775             }
 776         };
 777     };
 778 
 779     // 服务员,负责组合菜单,负责组装每个菜和具体的实现者。
 780     var Waiter = function () {
 781         var menuCommand = new MenuCommand();
 782 
 783         // 客户点菜
 784         this.orderDish = function (cmd) {
 785             var hotCook = new HotCook();
 786             var coolCook = new CoolCook();
 787 
 788             if (cmd instanceof DuckCommand) {
 789                 cmd.setCookApi(hotCook);
 790             } else if (cmd instanceof ChopCommand) {
 791                 cmd.setCookApi(hotCook);
 792             } else if (cmd instanceof PorkCommand) {
 793                 cmd.setCookApi(coolCook);
 794             }
 795 
 796             menuCommand.addCommand(cmd);
 797         };
 798 
 799         // 点菜完毕
 800         this.orderOver = function () {
 801             menuCommand.execute();
 802         };
 803     };
 804 
 805     var waiter = new Waiter();
 806     var chop = new ChopCommand();
 807     var duck = new DuckCommand();
 808     var pork = new PorkCommand();
 809 
 810     waiter.orderDish(chop);
 811     waiter.orderDish(duck);
 812     waiter.orderDish(pork);
 813 
 814     waiter.orderOver();
 815 
 816 }());
 817 
 818 (function () {
 819     // 队列请求
 820 
 821     function createCommand(name) {
 822         function Command(tableNum) {
 823             this.cookApi = null;
 824             this.tableNum = tableNum;
 825         }
 826 
 827         Command.prototype = {
 828             setCookApi: function (cookApi) {
 829                 this.cookApi = cookApi;
 830             },
 831             execute: function () {
 832                 this.cookApi.cook(this.tableNum, name);
 833             }
 834         };
 835 
 836         return Command;
 837     }
 838 
 839     var ChopCommand = createCommand('绿豆排骨煲');
 840     var DuckCommand = createCommand('北京烤鸭');
 841 
 842     var CommandQueue = {
 843         cmds: [],
 844         addMenu: function (menu) {
 845             var cmds = menu.getCommands();
 846             for (var i = 0, len = cmds.length; i < len; i++) {
 847                 this.cmds.push(cmds[i]);
 848             }
 849         },
 850         getOneCommand: function () {
 851             return this.cmds.length ? this.cmds.shift() : null;
 852         }
 853     };
 854 
 855     var MenuCommand = function () {
 856         this.col = [];
 857     };
 858     MenuCommand.prototype = {
 859         addCommand: function (cmd) {
 860             this.col.push(cmd);
 861         },
 862         setCookApi: function (cookApi) {
 863         },
 864         getTableNum: function () {
 865             return 0;
 866         },
 867         getCommands: function () {
 868             return this.col;
 869         },
 870         execute: function () {
 871             CommandQueue.addMenu(this);
 872         }
 873     };
 874 
 875     var HotCook = function (name) {
 876         this.name = name;
 877     };
 878     HotCook.prototype = {
 879         cook: function (tableNum, name) {
 880             var cookTime = parseInt(10 * Math.random() + 3);
 881             console.log(this.name + '厨师正在为' + tableNum + '号桌做:' + name);
 882 
 883             var me = this;
 884             setTimeout(function () {
 885                 console.log(me.name + '厨师为' + tableNum + '号桌做好了:' + name + ',共计耗时=' + cookTime + '');
 886             }, cookTime * 1000);
 887         },
 888         run: function () {
 889             var me = this;
 890             setTimeout(function () {
 891                 var cmd;
 892 
 893                 while ((cmd = CommandQueue.getOneCommand())) {
 894                     cmd.setCookApi(me);
 895                     cmd.execute();
 896                 }
 897             }, 1000);
 898         }
 899     };
 900 
 901     var Waiter = function () {
 902         this.menuCommand = new MenuCommand();
 903     };
 904     Waiter.prototype = {
 905         orderDish: function (cmd) {
 906             this.menuCommand.addCommand(cmd);
 907         },
 908         orderOver: function () {
 909             this.menuCommand.execute();
 910         }
 911     };
 912 
 913     var c1 = new HotCook('张三');
 914     c1.run();
 915 
 916     for (var i = 0; i < 5; i++) {
 917         var waiter = new Waiter();
 918         var chop = new ChopCommand(i);
 919         var duck = new DuckCommand(i);
 920 
 921         waiter.orderDish(chop);
 922         waiter.orderDish(duck);
 923 
 924         waiter.orderOver();
 925     }
 926 
 927 }());
 928 
 929 function test() {
 930     // 日志请求
 931     // TODO 该示例在写入文件内容的时候并不能把实例的原型对象序列化,
 932     // 因此读取文件内容后,反序列化后没有原型对应的方法
 933     var fs = require('fs');
 934     var Promise = require('d:\\node\\node_modules\\rsvp');
 935 
 936     var FileOpeUtil = {
 937         readFile: function (pathName) {
 938             var def = Promise.defer();
 939 
 940             fs.open(pathName, 'r', function opened(err, fd) {
 941                 if (err) {
 942                     def.reject();
 943                     fs.close(fd);
 944                     throw err;
 945                 }
 946 
 947                 var readBuffer = new Buffer(1024);
 948                 var bufferOffset = 0;
 949                 var bufferLength = readBuffer.length;
 950                 var filePosition = null;
 951 
 952                 fs.read(
 953                     fd,
 954                     readBuffer,
 955                     bufferOffset,
 956                     bufferLength,
 957                     filePosition,
 958                     function read(err, readBytes) {
 959                         if (err) {
 960                             def.reject(err);
 961                             fs.close(fd);
 962                             return;
 963                         }
 964 
 965                         if (readBytes >= 0) {
 966                             try {
 967                                 def.resolve(JSON.parse(readBuffer.slice(0, readBytes).toString('utf8')));
 968                             } catch (e) {
 969                                 def.reject(e);
 970                             }
 971 
 972                             fs.close(fd);
 973                         }
 974                     }
 975                 );
 976             });
 977 
 978             return def.promise;
 979         },
 980         writeFile: function (pathName, list) {
 981             var def = Promise.defer();
 982 
 983             fs.open(pathName, 'w', function opened(err, fd) {
 984                 if (err) {
 985                     def.reject();
 986                     fs.close(fd);
 987                     throw err;
 988                 }
 989 
 990                 var writeBuffer = new Buffer(JSON.stringify(list));
 991                 var bufferPosition = 0;
 992                 var bufferLength = writeBuffer.length;
 993                 var filePosition = null;
 994 
 995                 fs.write(
 996                     fd,
 997                     writeBuffer,
 998                     bufferPosition,
 999                     bufferLength,
1000                     filePosition,
1001                     function wrote(err, written) {
1002                         if (err) {
1003                             def.reject(err);
1004                             fs.close(fd);
1005                             return;
1006                         }
1007 
1008                         console.log('wrote ' + written + ' bytes');
1009                         def.resolve(written);
1010                         fs.close(fd);
1011                     }
1012                 );
1013             });
1014 
1015             return def.promise;
1016         }
1017     };
1018 
1019     function createCommand(name) {
1020         function Command(tableNum) {
1021             this.cookApi = null;
1022             this.tableNum = tableNum;
1023         }
1024 
1025         Command.prototype = {
1026             setCookApi: function (cookApi) {
1027                 this.cookApi = cookApi;
1028             },
1029             execute: function () {
1030                 this.cookApi.cook(this.tableNum, name);
1031             }
1032         };
1033 
1034         return Command;
1035     }
1036 
1037     var ChopCommand = createCommand('绿豆排骨煲');
1038     var DuckCommand = createCommand('北京烤鸭');
1039 
1040     var MenuCommand = function () {
1041         this.col = [];
1042     };
1043     MenuCommand.prototype = {
1044         addCommand: function (cmd) {
1045             this.col.push(cmd);
1046         },
1047         setCookApi: function (cookApi) {
1048         },
1049         getTableNum: function () {
1050             return 0;
1051         },
1052         getCommands: function () {
1053             return this.col;
1054         },
1055         execute: function () {
1056             CommandQueue.addMenu(this);
1057         }
1058     };
1059 
1060     var HotCook = function (name) {
1061         this.name = name;
1062     };
1063     HotCook.prototype = {
1064         cook: function (tableNum, name) {
1065             var cookTime = parseInt(10 * Math.random() + 3);
1066             console.log(this.name + '厨师正在为' + tableNum + '号桌做:' + name);
1067 
1068             var me = this;
1069             setTimeout(function () {
1070                 console.log(me.name + '厨师为' + tableNum + '号桌做好了:' + name + ',共计耗时=' + cookTime + '');
1071             }, cookTime * 1000);
1072         },
1073         run: function () {
1074             var me = this;
1075             setTimeout(function () {
1076                 var cmd;
1077 
1078                 while ((cmd = CommandQueue.getOneCommand())) {
1079                     cmd.setCookApi(me);
1080                     cmd.execute();
1081                     break;
1082                 }
1083             }, 1000);
1084         }
1085     };
1086 
1087     var Waiter = function () {
1088         this.menuCommand = new MenuCommand();
1089     };
1090     Waiter.prototype = {
1091         orderDish: function (cmd) {
1092             this.menuCommand.addCommand(cmd);
1093         },
1094         orderOver: function () {
1095             this.menuCommand.execute();
1096         }
1097     };
1098 
1099 
1100     var CommandQueue = {
1101         cmds: [],
1102         addMenu: function (menu) {
1103             var cmds = menu.getCommands();
1104             for (var i = 0, len = cmds.length; i < len; i++) {
1105                 this.cmds.push(cmds[i]);
1106             }
1107             FileOpeUtil.writeFile('./test.txt', this.cmds);
1108         },
1109         getOneCommand: function () {
1110             var cmd = null;
1111 
1112             if (this.cmds.length) {
1113                 cmd = this.cmds.shift();
1114                 FileOpeUtil.writeFile('./test.txt', this.cmds);
1115             }
1116 
1117             return cmd;
1118         }
1119     };
1120 
1121     var FILE_NAME = './test.txt';
1122 
1123     FileOpeUtil.readFile(FILE_NAME)
1124         .then(function (data) {
1125             console.log(data);
1126             data.map(function () {
1127 
1128             });
1129 
1130             CommandQueue.cmds = data;
1131             main();
1132         }, function () {
1133             main();
1134         });
1135 
1136     function main() {
1137         var c1 = new HotCook('张三');
1138         c1.run();
1139 
1140         for (var i = 0; i < 5; i++) {
1141             var waiter = new Waiter();
1142             var chop = new ChopCommand(i);
1143             var duck = new DuckCommand(i);
1144 
1145             waiter.orderDish(chop);
1146             waiter.orderDish(duck);
1147 
1148             waiter.orderOver();
1149         }
1150     }
1151 }
1152 
1153 /*
1154  用于崩溃恢复的命令日志
1155 
1156  命令日志的一个有趣的用途是在程序崩溃后恢复其状态。在前面这个示例中,可以用XHR把经过序列化处理的命令记录到服务器上。用户下次访问该网页的时候,系统可以找出这些命令并用其将画布上的图案精确恢复到浏览器关闭时的状态。这可以替用户把应用程序状态保管下来,以便其撤销先前的任何一次浏览器会话中执行的操作。如果应用系统比较复杂,那么这种类型的命令日志会很大的存储需求。为此你可以提供一个按钮,用户可以用它提交到当时为止的所有操作,从而清空命令栈。
1157  */
1158 
1159 /*
1160  命令模式的适用场合
1161 
1162  1.如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
1163  2.如果需要在不同的时刻指定,排列和执行请求。将这些请求封装成为命令对象,然后实现请求队列化。
1164  3.如果需要支持取消操作,可以选用,通过管理命令对象,能很容易的实现命令的恢复和重做功能。
1165  4.如果需要支持当系统奔溃时,能将系统的操作功能重新执行一遍时。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
1166  5.在需要事务的系统中,命令模式提供了对事务进行建模的方法。
1167 
1168 
1169  命令模式之利
1170 
1171  1.更松散的耦合
1172  命令模式使得发起命令的对象--客户端,和具体实现命令的对象--接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
1173  2.更动态的控制
1174  命令模式把请求封装起来,可以动态地对它进行参数化,队列花和日志化等操作,从而使得系统更灵活。
1175  3.很自然的复合命令
1176  很容易地组合符合命令,也就是宏命令。
1177  4.更好的扩展性
1178  
1179 
1180 
1181  命令模式之弊
1182 
1183  如果一个命令对象只包装了一个方法调用,而且其唯一目的就是这层对象包装的话,那么这种做法是一种浪费。如果你不需要命令模式给予的任何额外特性,也不需要具有一致接口的类所带来的模块性,那么直接使用方法引用而不是完整的命令对象也许更恰当。命令对象也会增加代码调试的难度,因为在应用了命令模式之后原有的方法之上又多了一层可能出错的代码。
1184 
1185 
1186  相关模式
1187 
1188  命令模式和组合模式
1189  可以组合使用
1190  宏命令的功能就可以使用组合模式。
1191 
1192  命令模式和备忘录模式
1193  可以组合使用
1194  在实现可撤销功能时,如果采用保存命令执行前的状态,撤销的时候就把状态恢复,就可以考虑使用备忘录模式。
1195 
1196  命令模式和模板方法模式
1197  命令模式可以作为模板方法的一种替代模式,也就是说命令模式可以模仿实现模板方法模式的功能。
1198  */
1199 
1200 
1201 /* Title: Command
1202  Description: creates objects which encapsulate actions and parameters
1203  */
1204 
1205 (function () {
1206 
1207     var CarManager = {
1208 
1209         /* request information */
1210         requestInfo: function (model, id) {
1211             return 'The purchase info for ' + model + ' with ID ' + id + ' is being processed...';
1212         },
1213 
1214         /* purchase the car */
1215         buyVehicle: function (model, id) {
1216             return 'You have successfully purchased Item ' + id + ', a ' + model + '.';
1217         }
1218 
1219     };
1220 
1221     CarManager.execute = function (commad) {
1222         return CarManager[commad.request](commad.model, commad.carID);
1223     };
1224 
1225     var actionA = CarManager.execute({request: 'requestInfo', model: 'Ford Mondeo', carID: '543434'});
1226     console.log(actionA);
1227     var actionB = CarManager.execute({request: 'buyVehicle', model: 'Ford Mondeo', carID: '543434'});
1228     console.log(actionB);
1229 
1230 })();
1231 
1232 
1233 // http://www.joezimjs.com/javascript/javascript-design-patterns-command/
1234 var EnableAlarm = function (alarm) {
1235     this.alarm = alarm;
1236 }
1237 EnableAlarm.prototype.execute = function () {
1238     this.alarm.enable();
1239 }
1240 
1241 var DisableAlarm = function (alarm) {
1242     this.alarm = alarm;
1243 }
1244 DisableAlarm.prototype.execute = function () {
1245     this.alarm.disable();
1246 }
1247 
1248 var ResetAlarm = function (alarm) {
1249     this.alarm = alarm;
1250 }
1251 ResetAlarm.prototype.execute = function () {
1252     this.alarm.reset();
1253 }
1254 
1255 var SetAlarm = function (alarm) {
1256     this.alarm = alarm;
1257 }
1258 SetAlarm.prototype.execute = function () {
1259     this.alarm.set();
1260 }
1261 
1262 var alarms = [/* array of alarms */],
1263     i = 0, len = alarms.length;
1264 
1265 for (; i < len; i++) {
1266     var enable_alarm = new EnableAlarm(alarms[i]),
1267         disable_alarm = new DisableAlarm(alarms[i]),
1268         reset_alarm = new ResetAlarm(alarms[i]),
1269         set_alarm = new SetAlarm(alarms[i]);
1270 
1271     new Button('enable', enable_alarm);
1272     new Button('disable', disable_alarm);
1273     new Button('reset', reset_alarm);
1274     new Button('set', set_alarm);
1275 }
1276 
1277 
1278 var makeEnableCommand = function (alarm) {
1279     return function () {
1280         alarm.enable();
1281     }
1282 }
1283 
1284 var makeDisableCommand = function (alarm) {
1285     return function () {
1286         alarm.disable();
1287     }
1288 }
1289 
1290 var makeResetCommand = function (alarm) {
1291     return function () {
1292         alarm.reset();
1293     }
1294 }
1295 
1296 var makeSetCommand = function (alarm) {
1297     return function () {
1298         alarm.set();
1299     }
1300 }
1301 
1302 </script>
1303 </body>
1304 </html>

 

posted @ 2013-05-13 15:43  LukeLin  阅读(808)  评论(0编辑  收藏  举报