Detailed explanation of the command mode in Javascript practice

Detailed explanation of the command mode in Javascript practice

definition

Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.“

The "command pattern" encapsulates "requests" into objects so that other objects can be parameterized using different requests, queues, or logs, while supporting undoable operations.

The definition of "request" here is not the "Ajax request" that we often say on the front end, but an "action request", which means initiating an action. For example, when you turn off the TV using the remote control, "turn off" is a request. In the command pattern, we abstract the request into a command, which is reusable and only cares about its recipient (the TV). As for the initiator of the action (the remote control), it only cares about which commands it supports, but not what these commands do specifically.

structure

The class diagram of the command pattern is as follows:

In this class diagram, we see five roles:

  • Client - Create Concrete Command and Receiver (application layer).
  • Invoker - The issuer of the command, usually holds a command object and can hold many command objects.
  • Receiver - Command receiver, the object that actually executes the command. Any class may become a receiver as long as it can implement the corresponding functions required by the command.
  • Command - Command interface.
  • ConcreteCommand - Implementation of the Command interface.

Receiver and Invoker are not coupled. When the function needs to be expanded, a new Command is added. Therefore, the command mode complies with the open-closed principle.

Examples

Custom shortcut keys

Customizing shortcut keys is the most basic function of an editor. Through the command pattern, we can write a structure that decouples the key position from the key logic.

interface Command {
    exec():void
}

type Keymap = { [key:string]: Command }
class Hotkey {
    keymap: Keymap = {}

    constructor(keymap: Keymap) {
        this.keymap = keymap
    }

    call(e: KeyboardEvent) {
        const prefix = e.ctrlKey ? 'ctrl+' : ''
        const key = prefix + e.key
        this.dispatch(key)
    }

    dispatch(key: string) {
        this.keymap[key].exec()
    }
}

class CopyCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

class CutCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

class PasteCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

const clipboard = { data: '' }
const keymap = {
    'ctrl+x': new CutCommand(clipboard),
    'ctrl+c': new CopyCommand(clipboard),
    'ctrl+v': new PasteCommand(clipboard)
}
const hotkey = new Hotkey(keymap)

document.onkeydown = (e) => {
    hotkey.call(e)
}

In this case, hotkey is the Invoker and clipboard is the Receiver. When we need to modify an existing keymap, we only need to add or replace the existing key or command.

Do you think this writing style is familiar? Yes, Redux also applies the command mode. Store is equivalent to Receiver, Action is equivalent to Command, and Dispatch is equivalent to Invoker.

Undo and Redo

Based on the command pattern, we can easily extend it to support undo and redo.

interface IPerson {
    moveTo(x: number, y: number): void
}

class Person implements Person {
    x = 0
    y = 0

    moveTo(x: number, y: number) {
        this.x = x
        this.y = y
    }
}

interface Command {
    exec(): void
    undo(): void
}

class MoveCommand implements Command {
    prevX = 0
    prevY = 0

    person: Person

    constructor(person: Person) {
        this.person = person
    }

    exec() {
        this.prevX = this.person.x
        this.prevY = this.person.y
        this.person.moveTo(this.prevX++, this.prevY++)
    }

    undo() {
        this.person.moveTo(this.prevX, this.prevY)
    }
}


const ezio = new Person()
const moveCommand = new MoveCommand(ezio)
moveCommand.exec()
console.log(ezio.x, ezio.y)
moveCommand.undo()
console.log(ezio.x, ezio.y)

Recording and playback

Think about the recording and playback function in the game. If each action of the character is regarded as a command, then a series of command queues can be obtained during recording.

class Control {
    commands: Command[] = []
    
    exec(command) {
        this.commands.push(command)
        command.exec(this.person)
    }
}

const ezio = new Person()
const control = new Control()
control.exec(new MoveCommand(ezio))
control.exec(new MoveCommand(ezio))

console.log(control.commands)

When we have a command queue, we can easily perform multiple undo and redo operations and realize a command history. Just move the pointer of the current command queue.

class CommandHistory {
    commands: Command[] = []
    
    index = 0
    
    get currentCommand() {
        return this.commands[index]
    }
    
    constructor(commands: Command[]) {
        this.commands = commands
    }
    
    redo() {
        this.index++
        this.currentCommand.exec()
    }
    
    undo() {
        this.currentCommand.undo()
        this.index--
    }
}

At the same time, if we serialize the command into an object, it can be used for storage and transmission. In this way, we can send it to the remote computer and realize the function of remotely controlling the movement of ezio.

[{
    type: 'move',
    x: 1,
    y: 1,
}, {
    type: 'move',
    x: 2,
    y: 2,
}]

Macros

By doing some simple processing on the Command, you can combine the existing commands and execute them, turning them into a macro command.

class BatchedCommand implements Command {
    commands = []
    
    constructor(commands) {
        this.commands = commands
    }
    
    exec() {
        this.commands.forEach(command => command.exec())
    }
}

const batchedMoveCommand = new BatchedCommand([
    new MoveCommand(ezio),
    new SitCommand(ezio),
])

batchedMoveCommand.exec()

Summarize

Through the above examples, we can see that the command mode has the following characteristics:

  • Low coupling completely eliminates the coupling between the receiver and the caller.
  • Easy to expand, new functions can be expanded by simply adding new commands.
  • Supports serialization, making storage and transmission easy.
  • This can easily lead to a large Command class.

The above is a detailed explanation of the command mode in Javascript practice. For more information about the Javascript command mode, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Writing Maintainable Object-Oriented JavaScript Code
  • Using JavaScript to create maintainable slideshow code
  • Let's talk about what JavaScript's URL object is
  • Detailed explanation of the practical application of regular expressions in JavaScript
  • How to implement an array lazy evaluation library in JavaScript
  • How to make your own native JavaScript router
  • How to Learn Algorithmic Complexity with JavaScript
  • Use a few interview questions to look at the JavaScript execution mechanism
  • Teach you how to write maintainable JS code

<<:  Introduction to common MySQL storage engines and parameter setting and tuning

>>:  Implementation of Docker data volume operations

Recommend

JS Decorator Pattern and TypeScript Decorators

Table of contents Introduction to the Decorator P...

Vue keeps the user logged in (various token storage methods)

Table of contents How to set cookies Disadvantage...

Nginx source code compilation and installation process record

The installation of the rpm package is relatively...

Analysis of the cause of docker error Exited (1) 4 minutes ago

Docker error 1. Check the cause docker logs nexus...

MySQL index principle and usage example analysis

This article uses examples to illustrate the prin...

Vuex modularization and namespaced example demonstration

1. Purpose: Make the code easier to maintain and ...

Tutorial on installing phpMyAdmin under Linux centos7

yum install httpd php mariadb-server –y Record so...

IE conditional comments for XHTML

<br />Conditional comments are a feature uni...

Vue implements infinite loading waterfall flow

This article example shares the specific code of ...

Detailed explanation of the principle and usage of MySQL views

This article uses examples to illustrate the prin...