Typescript 命令模式(Command)

標籤: 前端 設計模式 命令模式 typescript Command


請仔細閱讀下面代碼,理解其中的設計理念。


命令模式

命令模式: 命令模式是一中封裝方法調用的方式,用來將行爲請求者和行爲實現者解除耦合。

實際場景

在軟件系統中,行爲請求者與行爲實現者通常是一種緊耦合的關係,但某些場合,比如需要對行爲進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適。

命令模式的結構

  • 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);

執行結果


命令模式的利弊

利:

  • 將行爲請求和行爲實現解耦。
  • 新的命令可以很容易添加到系統中去。

弊:

  • 使用命令模式可能會導致某些系統有過多的具體命令類。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章