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

How to use Nginx to carry rtmp live server

This time we set up an rtmp live broadcast server...

Build a severe weather real-time warning system with Node.JS

Table of contents Preface: Step 1: Find the free ...

Linux C log output code template sample code

Preface This article mainly introduces the releva...

Detailed explanation of the benefits of PNG in various network image formats

BMP is an image file format that is independent o...

Detailed explanation of MySQL multi-table join query

Table of contents Multi-table join query Inner Jo...

Should I abandon JQuery?

Table of contents Preface What to use if not jQue...

Tutorial diagram of installing centos7.3 on vmware virtual machine

VMware Preparation CentOS preparation, here is Ce...

Detailed explanation of docker visualization graphics tool portainer

Table of contents 1. Introduction to Portainer 2....

Example of how to create and run multiple MySQL containers in Docker

1. Use the mysql/mysql-server:latest image to qui...

The whole process of installing and configuring Harbor1.7 on CentOS7.5

1. Download the required packages wget -P /usr/lo...

Specific use of Linux gcc command

01. Command Overview The gcc command uses the C/C...

How to configure Hexo and GitHub to bind a custom domain name under Windows 10

Hexo binds a custom domain name to GitHub under W...