标签: 前端 设计模式 命令模式 typescript Command
请仔细阅读下面代码,理解其中的设计理念。
command.jpgcommand-client.jpg
命令模式
命令模式: 命令模式是一中封装方法调用的方式,用来将行为请求者和行为实现者解除耦合。
实际场景
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
命令模式的结构
- Client 客户
- Invoker 调用命令的对象
- Command 具体的命令
- Receiver 真正的命令执行对象
命令模式的例子
龟兔赛跑:
- 喜欢偷懒的兔子每个阶段移动0-60米
- 持之以恒的乌龟每个阶段移动10-50米
- 可以向右或者向左移动
- 每个阶段可以回滚
- 输出移动日志
- 支持添加更多参赛者
套用命令模式的结构,我们得到
Command: 向右移动(支持取消操作)、 向左移动(支持取消操作)...
Receiver: 兔子、乌龟、...
定义基本类型
/* command-interface.ts */
// 命令接口
export interface ICommand {
execute: (step: number) => any
undo: () => any
}
/* target-interface.ts */
// 参赛队员接口
export interface ITarget {
name: string
position: number
}
/* receiver-interface.ts */
// 接收者接口
export interface IRightReceiver {
moveRight: (step: number) => any;
rightUndo: () => any;
}
export interface ILeftReceiver {
moveLeft: (step: number) => any;
leftUndo: () => any;
}
/* command-class.ts */
// 命令类型
export enum CommandClass {
GO_RIGHT = 'GO_RIGHT',
GO_LEFT = 'GO_LEFT',
}
移动命令类
/* left-command.ts */
// 向左移动类
import { ILeftReceiver } from '../receiver-models/receiver-interface';
import { ICommand } from './command-interface';
export class LeftCommand implements ICommand {
protected receivers: ILeftReceiver[] = [];
public addReceiver (receiver: ILeftReceiver) {
this.receivers.push(receiver);
return this;
}
public removeReceiver (receiver: ILeftReceiver) {
const index = this.receivers.findIndex(item => item === receiver);
if (index !== -1) {
this.receivers.splice(index, 1);
}
}
public execute (step: number) {
this.receivers.map((receiver: ILeftReceiver) => {
receiver.moveLeft(step);
});
}
public undo () {
this.receivers.map((receiver: ILeftReceiver) => {
receiver.leftUndo();
});
}
}
/* right-command.ts */
// 向右移动类
import { IRightReceiver } from '../receiver-models/receiver-interface';
import { ICommand } from './command-interface';
export class RightCommand implements ICommand {
protected receivers: IRightReceiver[] = [];
public addReceiver (receiver: IRightReceiver) {
this.receivers.push(receiver);
return this;
}
public removeReceiver (receiver: IRightReceiver) {
const index = this.receivers.findIndex(item => item === receiver);
if (index !== -1) {
this.receivers.splice(index, 1);
}
}
public execute (step: number) {
this.receivers.map((receiver: IRightReceiver) => {
receiver.moveRight(step);
});
}
public undo () {
this.receivers.map((receiver: IRightReceiver) => {
receiver.rightUndo();
});
}
}
接受者类
/* rabbit-receiver.ts */
// 兔子
import { ILeftReceiver, IRightReceiver } from './receiver-interface';
import { ITarget } from './target-interface';
export class RabbitReceiver implements IRightReceiver, ILeftReceiver {
public rabbit: ITarget;
private rightLogs: number[] = [];
private leftLogs: number[] = [];
constructor (rabbit: ITarget) {
this.rabbit = rabbit;
}
private getPosition () {
console.log(`Now rabbit is at ${this.rabbit.position}`);
}
public moveRight (step: number) {
const moveStep = parseInt(Math.random() * 60) * step;
this.rabbit.position += moveStep;
this.rightLogs.unshift(moveStep);
console.log(`Rabbit move right ${moveStep}`);
this.getPosition();
}
public rightUndo () {
this.rabbit.position -= Number(this.rightLogs[0]);
this.rightLogs.shift();
this.getPosition();
}
public moveLeft (step: number) {
const moveStep = parseInt(Math.random() * 60) * step;
this.rabbit.position -= moveStep;
this.leftLogs.unshift(moveStep);
console.log(`Rabbit move left ${moveStep}`);
this.getPosition();
}
public leftUndo () {
this.rabbit.position += Number(this.leftLogs[0]);
this.leftLogs.shift();
this.getPosition();
}
}
/* turtle-receiver.ts */
// 乌龟
import { ILeftReceiver, IRightReceiver } from './receiver-interface';
import { ITarget } from './target-interface';
export class TurtleReceiver implements IRightReceiver, ILeftReceiver {
public turtle: ITarget;
private rightLogs: number[] = [];
private leftLogs: number[] = [];
constructor (turtle: ITarget) {
this.turtle = turtle;
}
private getPosition () {
console.log(`Now turtle is at ${this.turtle.position}`);
}
public moveRight (step: number) {
const moveStep = (parseInt(Math.random() * 30) + 10) * step;
this.turtle.position += moveStep;
this.rightLogs.unshift(moveStep);
console.log(`Turtle move right ${moveStep}`);
this.getPosition();
}
public rightUndo () {
this.turtle.position -= Number(this.rightLogs[0]);
this.rightLogs.shift();
this.getPosition();
}
public moveLeft (step: number) {
const moveStep = (parseInt(Math.random() * 30) + 10) * step;
this.turtle.position -= moveStep;
this.leftLogs.unshift(moveStep);
console.log(`Turtle move left ${moveStep}`);
this.getPosition();
}
public leftUndo () {
this.turtle.position += this.leftLogs[0](this.leftLogs[0]);
this.leftLogs.shift();
this.getPosition();
}
}
invoker
import { CommandClass } from './command-models/command-class';
import { LeftCommand } from './command-models/left-command';
import { RightCommand } from './command-models/right-command';
export class CommandLogModel {
id: number;
name: CommandClass;
step: number;
}
export class MoveInvoker {
private rightCommand: RightCommand;
private leftCommand: LeftCommand;
public logs: CommandLogModel[] = [];
constructor (commands: { rightCommand: RightCommand, leftCommand: LeftCommand }) {
this.rightCommand = commands.rightCommand;
this.leftCommand = commands.leftCommand;
}
public moveRight (step: number) {
console.log(`Do right command with step ${step}`);
this.rightCommand.execute(step);
this.logs.unshift({
id: Date.now(),
name: CommandClass.GO_RIGHT,
step: step
});
}
public moveLeft (step: number) {
console.log(`Do left command with step ${step}`);
this.leftCommand.execute(step);
this.logs.unshift({
id: Date.now(),
name: CommandClass.GO_LEFT,
step: step
});
}
public undo () {
console.log(`Do undo command`);
if (this.logs.length) {
const commandLog = this.logs[0];
switch (commandLog.name) {
case CommandClass.GO_RIGHT:
this.rightCommand.undo();
break;
case CommandClass.GO_LEFT:
this.leftCommand.undo();
break;
default:
}
this.logs.shift();
}
}
}
客户
import { LeftCommand } from './command-models/left-command';
import { RightCommand } from './command-models/right-command';
import { MoveInvoker } from './move-invoker';
import { RabbitReceiver } from './receiver-models/rabbit-receiver';
import { ITarget } from './receiver-models/target-interface';
import { TurtleReceiver } from './receiver-models/turtle-receiver';
export class Client {
public rabbit: ITarget = {
name: 'rabbit',
position: 0
};
public turtle: ITarget = {
name: 'turtle',
position: 0
};
public invoker: MoveInvoker;
constructor () {
const rightCommand = new RightCommand();
const leftCommand = new LeftCommand();
rightCommand.addReceiver(new RabbitReceiver(this.rabbit)).addReceiver(new TurtleReceiver(this.turtle));
leftCommand.addReceiver(new RabbitReceiver(this.rabbit)).addReceiver(new TurtleReceiver(this.turtle));
this.invoker = new MoveInvoker({rightCommand, leftCommand});
}
public moveRight (step: number) {
this.invoker.moveRight(step);
return this;
}
public moveLeft (step: number) {
this.invoker.moveLeft(step);
return this;
}
public getLogs () {
const logs = this.invoker.logs;
logs.map((log) => console.log(`${log.name}-${log.step}`));
return this;
}
public undo () {
this.invoker.undo();
return this;
}
}
new Client().moveRight(1).moveRight(1).undo().moveRight(2);
执行结果
competition-result.png
命令模式的利弊
利:
- 将行为请求和行为实现解耦。
- 新的命令可以很容易添加到系统中去。
弊:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
网友评论