Series catalog:

  • [1] — Creative design Patterns
  • JavaScript Design Pattern Parsing [2] — Structural design Pattern
  • JavaScript Design Pattern Parsing [3] — Behavioral design Patterns

Decorator pattern

Decorator mode is to dynamically add methods to objects while the program is running without changing the objects themselves.

The examples in this section require Babel to support the decorator pattern

The decorator pattern fits well with the dynamic nature of JavaScript because we can easily change an object, but at the same time, because functions are first-class citizens, we avoid rewriting a function directly to protect the maintainability and extensibility of our code.

In fact, it’s like the filter we add after taking a photo. Different filters give different mood to the photo. This is the decorator mode, which decorates the photo through the filter without modifying the photo itself.

Here’s an example:

The initial instance

You are considered brave at this point but the brave currently only has the initial number

class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }

    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return 'Attack power:The ${this.atk}, defense:The ${this.def}HP:The ${this.hp}, mana value:The ${this.mp}`}}const Reaper = new Warrior()

console.log('Brave state =>${Reaper}`) // Brave Status => Damage :50, Defense :50, Health: 100, Mana: 100
Copy the code

The use of decorators

We then equip the brave with a sword and create a decorateSword method, which is decorated on init

// The Object. DefineProperty method is used
function decorateSword(target, key, descriptor) {
    // Get init first
    const initMethod = descriptor.value
    // Sword adds 100 damage
    let moreAtk = 100
    let returnObj
    descriptor.value = (. args) = > {
        args[0] += moreAtk
        returnObj = initMethod.apply(target, args)
        return returnObj
    }
}



class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }
    @decorateSword
    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return 'Attack power:The ${this.atk}, defense:The ${this.def}HP:The ${this.hp}, mana value:The ${this.mp}`}}const Reaper = new Warrior()

console.log('Brave state =>${Reaper}`) // Brave Status => Damage :150, Defense: 50, Health: 100, Mana: 100
Copy the code

The stack of decorators

Right now, our hero’s defense is too low, we need to add another armor to the hero


/ / omit decorateSword

function decorateArmour(target, key, descriptor) {
    // Get init first
    const initMethod = descriptor.value
    // Armor adds 100 defense
    let moreDef = 100
    let returnObj
    descriptor.value = (. args) = > {
        args[1] += moreDef
        returnObj = initMethod.apply(target, args)
        return returnObj
    }
}


class Warrior {
    constructor(atk=50, def=50, hp=100, mp=100) {
        this.init(atk,def,hp,mp)
    }
    @decorateSword
    @decorateArmour
    init(atk, def, hp, mp) {
        this.atk = atk
        this.def = def
        this.hp = hp
        this.mp = mp
    }

    toString() {
        return 'Attack power:${this.atk}, defense:${this.def}HP:${this.hp}, mana value:${this.mp}`}}const Reaper = new Warrior()

console.log('Brave state =>${Reaper}`) // Warrior Status => Damage :150, Defense :150, Health: 100, Mana: 100
Copy the code

We have successfully upgraded the brave, and he can finally defeat the demon Lord (or a Slime).

Conclusion:

Decorators are also typically used to implement AOP (aspect oriented programming) using AOP the various parts of the business logic can be isolated, can also be isolated unrelated business report functions such as logging, exception handling, etc., so as to make the business logic between the parts of the coupling is reduced, improve the reusability of business irrelevant features, also improve the efficiency of development.

The decorator pattern is similar to the proxy pattern, but the intention of the proxy pattern is not to directly access the entity, to provide a substitute for the entity, to define key functions within the entity, and to provide or deny access to the entity, or some other additional thing. The purpose of decorator pattern is to dynamically add behavior to an object.

The appearance model

The facade pattern provides a higher level unified interface for a complex set of subsystem interfaces, through which access to subsystem interfaces is easier, and does not conform to the single responsibility principle and open closed principle.

In fact, the facade pattern is very common, it is a single function to simplify access to one or more larger, more complex functions, is a kind of encapsulation of complex operations.

Encapsulation Ajax

Initializing a native Ajax request is complex and can be simplified by encapsulation

function ajaxCall(type, url, callback, data) {
    let xhr = (function(){
        try {
            // Standard method
            return new XMLHttpRequest()
        }catch(e){}

        try {
            return new ActiveXObject("Msxm12.XMLHTTP")}catch(e){}
    }())
    STATE_LOADED = 4
    STATUS_OK = 200

    // As soon as a corresponding message indicating success is received from the server, the given callback method is executed
    xhr.onreadystatechange = function () {
        if(xhr.readyState ! == STATE_LOADED) {return
        }
        if (xhr.state == STATUS_OK) {
            callback(xhr.responseText)
        }
    }

    // Make a request
    xhr.open(type.toUpperCase(), url)
    xhr.send(data)
}
Copy the code

After encapsulation, we send the request like this

// Use the encapsulation method
ajaxCall("get"."/url/data".function(res) {
    document.write(res)
})
Copy the code

conclusion

The appearance mode is suitable for multiple complex operations at the same time. By encapsulating complex operations and calling them directly with methods, the code can be improved in terms of readability and maintainability.

The mediator pattern

The main role of the mediator pattern is to remove the strong coupling between objects. By adding a mediator, all objects communicate through the mediator instead of referring to each other, so when an object changes, only the mediator objects need to be notified.

The mediator transforms the netted many-to-many relationship into a relatively simple one-to-many relationship.

Example scenario

Let’s say two teams are playing league of Legends, and you have to kill all the other players to win. The following will be divided into blue and red:

class Player {
    constructor(name, teamColor) {
        this.name = name // Hero name
        this.teamColor = teamColor // Team color
        this.teammates = [] // List of teammates
        this.enemies = [] // List of enemies
        this.state = 'alive' // Live state
    }
    / / win
    win() {
        console.log(`Vicotry! The ${this.name}`)}/ / fail
    lose() {
        console.log(`Defeat! The ${this.name}`)}// Method of death
    die() {
        // Block out flag
        let ace_flag = true
        // Set the player state to dead
        this.state = 'dead'
        // Iterate through the list of teammates
        for(let i in this.teammates) {
            if (this.teammates[i].state ! = ='dead') {
                ace_flag = false
                break}}// If it has been destroyed
        if (ace_flag === true) {
            // Your side failed
            this.lose()
            for(let i in this.teammates) {
                this.teammates[i].lose()
            }
            // The enemy wins
            for(let i in this.enemies) {
                this.enemies[i].win()
            }
        }
    }
}
// Player list
const Players = []

// Define a factory function to generate players

function playerFactory (name, teamColor) {
    let newPlayer = new Player(name, teamColor)
    // Notify all players of the new character
    for(let i in Players) {
        if (Players[i].teamColor === teamColor) {
            Players[i].teammates.push(newPlayer)
            newPlayer.teammates.push(Players[i])
        } else {
            Players[i].enemies.push(newPlayer)
            newPlayer.enemies.push(Players[i])
        }
    }
    Players.push(newPlayer)
    return newPlayer
}

// Start the match
/ / blue party
let hero1 = playerFactory('galen'.'Blue')
let hero2 = playerFactory('prince'.'Blue')
let hero3 = playerFactory('Lacos'.'Blue')
let hero4 = playerFactory('sword she'.'Blue')
let hero5 = playerFactory('xenzhao'.'Blue')

/ / red square
let hero6 = playerFactory('hand,'.'Red')
let hero7 = playerFactory('Delevin'.'Red')
let hero8 = playerFactory(katarina.'Red')
let hero9 = playerFactory('the raven'.'Red')
let hero10 = playerFactory('after'.'Red')


// The red square is destroyed by the mass
hero6.die()
hero7.die()
hero8.die()
hero9.die()
hero10.die()


/* Result: Defeat! Thain Defeat! Defeat's hand! DE levin Defeat! Catalina Defeat! The crow Vicotry! Galen Vicotry! Prince Vicotry! Lacus Vicotry! Jian ji Vicotry! Xenzhao * /
Copy the code

However, this is only for one game, if we have a drop or team change, then the above situation can not be solved late in the night, so we need a mediator to control all players.

Refactoring using the mediator pattern

  • The Player and palyerFactory base operations remain the same
  • Assign the operation corner to the mediator object
const GameManager = ( function() {
    // Store all players
    const players = []
    // Operate entities
    const operations = {}
    // Add new players
    operations.addPlayer = function (player) {
        let teamColor = player.teamColor
        players[teamColor] = players[teamColor] || []; // If the player of this color does not already have a team, create a new team
        players[teamColor].push(player); // Add players to the team
    }
    // The player dropped the call
    operations.playerDisconnect = function (player) {
        // Player team color
        let teamColor = player.teamColor
        let teamPlayer = players[teamColor]
        for(let i in teamPlayer) {
            if (teamPlayer[i].name = player.name) {
                teamPlayer.splice(i, 1)}}}// The player dies
    operations.playerDead = function (player) {
        let teamColor = player.teamColor
        teamPlayers = players[teamColor]
        // Block out flag
        let ace_flag = true
        // Set the player state to dead
        this.state = 'dead'
        // Iterate through the list of teammates
        for(let i in teamPlayers) {
            if(teamPlayers[i].state ! = ='dead') {
                ace_flag = false
                break}}// If it has been destroyed
        if (ace_flag === true) {
            // Your side failed
            for(let i in teamPlayers) {
                teamPlayers[i].lose()
            }
            // The enemy wins
            for(let color in players) {
                if(color ! == teamColor) {let teamPlayers = players[color]
                    teamPlayers.map(player= > {
                        player.win()
                    })
                }
            }
        }
    }

    function reciveMessage (message, player) {
        operations[message](player)
    }

    return {
        reciveMessage: reciveMessage
    }
})()




class Player {
    constructor(name, teamColor) {
        this.name = name // Hero name
        this.teamColor = teamColor // Team color
        this.state = 'alive' // Live state
    }
    / / win
    win() {
        console.log(`Vicotry! The ${this.name}`)}/ / fail
    lose() {
        console.log(`Defeat! The ${this.name}`)}// Method of death
    die() {
        // Set the player state to dead
        this.state = 'dead'
        // Send a declaration of death to the intermediary
        GameManager.reciveMessage('playerDead'.this)}// The player dropped the call
    disconnect() {
        GameManager.reciveMessage('playerDisconnect'.this)}}// Player list
const Players = []

// Define a factory function to generate players

function playerFactory (name, teamColor) {
    let newPlayer = new Player(name, teamColor)
    // Notify the broker of new players
    GameManager.reciveMessage('addPlayer', newPlayer)
    return newPlayer
}

// Start the match
/ / blue party
let hero1 = playerFactory('galen'.'Blue')
let hero2 = playerFactory('prince'.'Blue')
let hero3 = playerFactory('Lacos'.'Blue')
let hero4 = playerFactory('sword she'.'Blue')
let hero5 = playerFactory('xenzhao'.'Blue')

/ / red square
let hero6 = playerFactory('hand,'.'Red')
let hero7 = playerFactory('Delevin'.'Red')
let hero8 = playerFactory(katarina.'Red')
let hero9 = playerFactory('the raven'.'Red')
let hero10 = playerFactory('after'.'Red')


// The red square is destroyed by the mass
hero6.die()
hero7.die()
hero8.die()
hero9.die()
hero10.die()

/* Result: Defeat! Thain Defeat! Defeat's hand! DE levin Defeat! Catalina Defeat! The crow Vicotry! Galen Vicotry! Prince Vicotry! Lacus Vicotry! Jian ji Vicotry! Xenzhao * /



Copy the code

When to use it?

The mediator mode is used to reduce the coupling, so if your code or module is highly coupled, overly dependent, and affects the actual call and maintenance, then you can use the mediator mode to reduce the coupling.

conclusion

  • The mediator pattern conforms to the least knowledge principle
  • The mediator pattern reduces the coupling between objects and modules
  • The mediator pattern transforms a complex mesh many-to-many model into a relatively simple one-to-many relationship.
  • The mediator model also has some disadvantages. For example, the mediator object is relatively complex and large, and sometimes the mediator itself is difficult to maintain.