美文网首页Web前端之路前端开发
Typescript 命令模式(Command)

Typescript 命令模式(Command)

作者: 我不叫奇奇 | 来源:发表于2019-01-17 19:35 被阅读4次

    标签: 前端 设计模式 命令模式 typescript Command


    请仔细阅读下面代码,理解其中的设计理念。

    command.jpg
    command-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

    命令模式的利弊

    利:

    • 将行为请求和行为实现解耦。
    • 新的命令可以很容易添加到系统中去。

    弊:

    • 使用命令模式可能会导致某些系统有过多的具体命令类。

    相关文章

      网友评论

        本文标题:Typescript 命令模式(Command)

        本文链接:https://www.haomeiwen.com/subject/njkwdqtx.html