preface

Recently, I learned about design patterns and TypeScript. I found that the materials on the Internet are a little hard core, which is not easy to understand and remember. I often forget after reading. As a game player, I find that many scenes in the game can be related to the corresponding design pattern, which is not only easy to understand, but also conducive to the rational use of the design pattern. Due to my limited personal level, I only arranged the design patterns that I thought were interesting, and each pattern was explained by three philosophical questions. If it helps you, you are welcome to like and bookmark 💖.

directory

Design patterns

The singleton pattern

What

Definition: A class has only one instance and provides a global point of access to it

Explanation: it essentially acts as a global variable that other classes and modules can only modify through the interface provided by the class

Game: During the days when DNF groups together to brush, the replica BOSS is treated as A singleton, and players can consume the BOSS through various skills or leveling A.

And all the players in this game deal damage to the same BOSS

How

  • BOSSIs a singleton and is instantiated only once
  • Different players use different attacks against each otherBOSSdamage
  • BOSSDamage taken is the total amount of damage done by the player

TS version

class Boss{
    private static instance: Boss = null;
    private hp: number = 1000;

    getInjured(harm: number) {this.hp -= harm;
    }
    getHp(): number{
        return this.hp;
    }
    static getInstance(): Boss{
        // Return if it is already instantiated
        if(!this.instance){
            this.instance = new Boss();
        }
        return this.instance; }}class Player{
    constructor(private name: string){}
    attack(harm: number,boss: Boss): void{
        boss.getInjured(harm);
        console.log('I'm a${this.name}, played the${harm}Damage,BOSS left${boss.getHp()}Blood `)}}let p1: Player = new Player('devil may cry');
let p2: Player = new Player('I');
let p3: Player = new Player('Asura');
// Output crazy to the same boss
p1.attack(100,Boss.getInstance());
p2.attack(80,Boss.getInstance());
p3.attack(120,Boss.getInstance());

// I'm a ghost cry. I dealt 100 damage and the BOSS had 900 health left
// I'm a street fighter, dealt 80 damage,BOSS has 820 health left
// I'm an asura, dealt 120 damage,BOSS has 700 health left
Copy the code

JS version

var Boss = /** @class */ (function () {
    function Boss() {
        this.hp = 1000;
    }
    Boss.prototype.getInjured = function (harm) {
        this.hp -= harm;
    };
    Boss.prototype.getHp = function () {
        return this.hp;
    };
    Boss.getInstance = function () {
        // Return if it is already instantiated
        if (!this.instance) {
            this.instance = new Boss();
        }
        return this.instance;
    };
    Boss.instance = null;
    returnBoss; } ());var Player = /** @class */ (function () {
    function Player(name) {
        this.name = name;
    }
    Player.prototype.attack = function (harm, boss) {
        boss.getInjured(harm);
        console.log("I am a" + this.name + "It's out." + harm + "Damage, BOSS left." + boss.getHp() + "Blood");
    };
    returnPlayer; } ());var p1 = new Player('devil may cry');
var p2 = new Player('I');
var p3 = new Player('Asura');
// Output crazy to the same boss
p1.attack(100, Boss.getInstance());
p2.attack(80, Boss.getInstance());
p3.attack(120, Boss.getInstance());

// I'm a ghost cry. I dealt 100 damage and the BOSS had 900 health left
// I'm a street fighter, dealt 80 damage,BOSS has 820 health left
// I'm an asura, dealt 120 damage,BOSS has 700 health left
Copy the code

Why

  • advantages
    • The singleton mode provides a unique interface to access the shared resource, avoiding multiple occupation of the shared resource
    • The singleton mode instantiates the object only once, saving memory
  • disadvantages
    • Abuse of the singleton pattern is the same as abuse of global variablesGCAutomatic recovery
    • Complex functionality in a singleton pattern can violate the single responsibility principle
  • Application scenario: Some static shared resources can be accessed in singleton mode. Here you can see that the game example itself is not a good fit for the singleton pattern (because it is a frequently changing object)

The strategy pattern

What

Definition: The policy pattern refers to defining a set of algorithms and encapsulating them one by one. Separating the unchanging from the changing is the theme of every design pattern, and the policy pattern is no exception. The purpose of the policy pattern is to separate the use of an algorithm from the implementation of the algorithm.

Explanation: As the saying goes, all roads lead to Rome, so called strategy mode is to achieve a function and adopt different strategy algorithm

In Hearthstone, the roguer’s two classic sets of cards, the Miracle Thief and the Burst Thief, are both great for roguers to win, but they have very different ways of winning. Miracle thief rely on a round of extreme operation can often save the day, let the opposite suddenly died; And the card thief is to fatigue and burst the opposite key card operation mode to win.

How

  • Miracle thieves and Smash thieves are both rogue decks
  • The miracle thief relies on one turn of extreme action
  • Breakouts rely on long hours of operation

TS version

interface Deck{
    name: string;
    kill(): void; 
}

class MiracleDeck implements Deck{
    name: string = 'Miracle Thief';
    kill(){
        console.log(`${this.name}: Seventeen cards are seconds you); }}class ExplosiveDeck implements Deck{
    name: string = 'Card Breaker';
    kill(){
        console.log(`${this.name}: I'm going to blow up your library! `)}}class Robber{
    private deck: Deck;
    setDeck(deck: Deck){
        this.deck = deck;
    } 
    winTheGame(): void{
        this.deck.kill();
    };
 }

 let rb = new Robber();
 rb.setDeck(new MiracleDeck());
 rb.winTheGame();
 rb.setDeck(new ExplosiveDeck());
 rb.winTheGame();

// Miracle Thief: seventeen cards are enough to catch you
// Card breaker: I'm going to blow up your library!
Copy the code

JS version

var MiracleDeck = /** @class */ (function () {
    function MiracleDeck() {
        this.name = 'Miracle Thief';
    }
    MiracleDeck.prototype.kill = function () {
        console.log(this.name + "Seventeen cards are what you need.");
    };
    returnMiracleDeck; } ());var ExplosiveDeck = /** @class */ (function () {
    function ExplosiveDeck() {
        this.name = 'Card Breaker';
    }
    ExplosiveDeck.prototype.kill = function () {
        console.log(this.name + "I'm gonna blow up your library!");
    };
    returnExplosiveDeck; } ());var Robber = /** @class */ (function () {
    function Robber() {
    }
    Robber.prototype.setDeck = function (deck) {
        this.deck = deck;
    };
    Robber.prototype.winTheGame = function () {
        this.deck.kill();
    };
    ;
    returnRobber; } ());var rb = new Robber();
rb.setDeck(new MiracleDeck());
rb.winTheGame();
rb.setDeck(new ExplosiveDeck());
rb.winTheGame();
// Miracle Thief: seventeen cards are enough to catch you
// Card breaker: I'm going to blow up your library!
Copy the code

Why

  • advantages
    • The algorithm can be switched freely and has good scalability
  • disadvantages
    • The policy classes grow and each policy class needs to be exposed
  • Application scenarios: Polymorphic scenarios, such as sharing, are implemented differently on different platforms

The proxy pattern

What

Definition: Provide a proxy for an object to control access to it

Explanation: For example, the relationship between the star and the agent, the agent will help the star to deal with the details such as sponsorship of commercial performance, the star is responsible for signing

Game: A lazy FM player who just wants to spend his time watching simulation games and is happy to leave the rest to the assistant coach

How

  • The player just needs to set tactics and watch the match
  • The assistant coach is responsible for the pre-match orientation, the team talk and the post-match press conference

TS version

interface Match{
    play(): void;
}

class Manager implements Match{
    play(): void{
        console.log('The match is on. It's Real Madrid versus Bayern Munich... ')}}class Cotch implements Match{
    private manager: Manager = new Manager();
    beforePlay(): void{
        console.log('Lay tactics');
        console.log('Team talk');
    }
    afterPlay(): void{
        console.log('Post-match interview');
    }
    play(): void{
        this.beforePlay();
        this.manager.play();
        this.afterPlay(); }}let proxy: Cotch = new Cotch();
proxy.play();

// Set tactics
// Team talk
// The match began. It was between Real Madrid and Bayern Munich...
// Post-match interviews
Copy the code

JS version

var Manager = /** @class */ (function () {
    function Manager() {
    }
    Manager.prototype.play = function () {
        console.log('The match is on. It's Real Madrid versus Bayern Munich... ');
    };
    returnManager; } ());var Cotch = /** @class */ (function () {
    function Cotch() {
        this.manager = new Manager();
    }
    Cotch.prototype.beforePlay = function () {
        console.log('Lay tactics');
        console.log('Team talk');
    };
    Cotch.prototype.afterPlay = function () {
        console.log('Post-match interview');
    };
    Cotch.prototype.play = function () {
        this.beforePlay();
        this.manager.play();
        this.afterPlay();
    };
    returnCotch; } ());var proxy = new Cotch();
proxy.play();
// Set tactics
// Team talk
// The match began. It was between Real Madrid and Bayern Munich...
// Post-match interviews
Copy the code

Why

  • advantages
    • Categorize real objects and target objects
    • Reduce the coupling degree of code, good scalability
  • disadvantages
    • The number of classes increases, which increases system complexity and reduces system performance
  • Application scenarios: can intercept some requests, such asES6In theProxy

Publish and subscribe

What

Definition: A one-to-many relationship between objects that allows multiple observer objects to listen to a topic object at the same time, so that all dependent objects are notified when an object changes

Explanation: Such as addEventListener in JS

Game: After entering the Game, you will subscribe to the survival state of your teammates and enemies, and the system will notify you when the survival state changes

How

  • Players need to subscribe to the status of their teammates and enemies
  • The state needs to be triggered
  • The system will notify you of the changed status

TS version

class EventEmitter{
    private events: Object = {};    // Store events
    private key: number = 0;    // The event is uniquely identified by the key

    on(name: string,event: any) :number{
        event.key = ++this.key;
        this.events[name] ? this.events[name].push(event) 
                          : (this.events[name] = []) && this.events[name].push(event);
        return this.key;
    }

    off(name: string,key: number) {if(this.events[name]){
            this.events[name] = this.events[name].filter(x= >x.key ! == key); }else{
            this.events[name] = [];
        }
    }

    emit(name: string,key? :number) {if(this.events[name].length === 0 ) throw Error('Sorry, you didn't define it${name}Listener `)
        if(key){
            this.events[name].forEach(x= > x.key === key && x());
        }else {
            this.events[name].forEach(x= >x()); }}}let player: EventEmitter = new EventEmitter();
player.on('friendDied'.function() :void{
    console.log('Your lovely teammate has been killed.');
})
player.on('enemyDied'.function() :void{
    console.log('Well done, handsome boy.')})// Simulate the war situation
let rand: number;
let k: number = 1;
while(k < 10){
    rand = Math.floor(Math.random() * 10);
    if(rand % 2= = =0){
        player.emit('friendDied');
    }else{
        player.emit('enemyDied');
    }
    k++;
}

// Your lovely teammate has been killed
// Nice call, handsome boy
// Your lovely teammate has been killed
// Your lovely teammate has been killed
// Nice call, handsome boy
// Your lovely teammate has been killed
// Your lovely teammate has been killed
// Nice call, handsome boy
// Nice call, handsome boy
Copy the code

JS version

var EventEmitter = /** @class */ (function () {
    function EventEmitter() {
        this.events = {}; // Store events
        this.key = 0; // The event is uniquely identified by the key
    }
    EventEmitter.prototype.on = function (name, event) {
        event.key = ++this.key;
        this.events[name] ? this.events[name].push(event)
            : (this.events[name] = []) && this.events[name].push(event);
        return this.key;
    };
    EventEmitter.prototype.off = function (name, key) {
        if (this.events[name]) {
            this.events[name] = this.events[name].filter(function (x) { returnx.key ! == key; }); }else {
            this.events[name] = []; }}; EventEmitter.prototype.emit =function (name, key) {
        if (this.events[name].length === 0)
            throw Error("\u62B1\u6B49\uFF0C\u4F60\u6CA1\u6709\u5B9A\u4E49 " + name + "\u76D1\u542C\u5668");
        if (key) {
            this.events[name].forEach(function (x) { return x.key === key && x(); });
        }
        else {
            this.events[name].forEach(function (x) { returnx(); }); }};returnEventEmitter; } ());var player = new EventEmitter();
player.on('friendDied'.function () {
    console.log('Your lovely teammate has been killed.');
});
player.on('enemyDied'.function () {
    console.log('Well done, handsome boy.');
});
// Simulate the war situation
var rand;
var k = 1;
while (k < 10) {
    rand = Math.floor(Math.random() * 10);
    if (rand % 2= = =0) {
        player.emit('friendDied');
    }
    else {
        player.emit('enemyDied');
    }
    k++;
}
// Your lovely teammate has been killed
// Nice call, handsome boy
// Your lovely teammate has been killed
// Your lovely teammate has been killed
// Nice call, handsome boy
// Your lovely teammate has been killed
// Your lovely teammate has been killed
// Nice call, handsome boy
// Nice call, handsome boy

Copy the code

Why

  • advantages
    • Very good implementation of event push, establish a complete triggering mechanism
  • disadvantages
    • If there are too many subscriptions, there is a delay in triggering
  • Application scenarios:JSIn theaddEventListenerandReduxData flow model in

The mediator model

What

Definition: a mediation object that encapsulates an interaction between a set of objects, making the original objects loosely coupled and able to change their interactions independently.

Explanation: simply put, it is the intermediary of communication between modules

In Game: FIFA Online, you can put a player card on the trading system and the goods will be forwarded to other players

How

  • Player 1 has a player card
  • The transaction intermediary system forwards information
  • Player 2,3 receives a message about a new product

TS version

abstract class Shop {
    / / store
    public fifaers: Object = {}
    / / register
    public register(name: string, fifaer: Fifaer): void {
        if (this.fifaers[name]) {
            console.error(`${name}The name already exists);
        } else {
            this.fifaers[name] = fifaer;
            fifaer.setMedium(this); }}/ / forwarding
    publicrelay(fifaer: Fifaer, message? :any) :void {
        Object.keys(this.fifaers).forEach((name: string) = > {
            if (this.fifaers[name] ! == fifaer) {this.fifaers[name].receive(message); }}}})// Abstract player class
abstract class Fifaer {
    protected mediator: Shop;
    public setMedium(mediator: Shop): void {
        this.mediator = mediator;
    }
    publicreceive(message? :any) :void {
        console.log(this.constructor.name + "Request received:", message);
    }
    publicsend(message? :any) :void {
        console.log(this.constructor.name + "New Cards on sale:", message);
        this.mediator.relay(this, message); // Ask the intermediary to forward it}}// The specific intermediary
class ConcreteShop extends Shop {
    constructor() {
        super()}}// Player 1
class Fifaer1 extends Fifaer {
    constructor() {
        super()}publicreceive(message? :any) :void {
        console.log(`${message}It's too expensive for me)}}// Player 2
class Fifaer2 extends Fifaer {
    constructor() {
        super()}publicreceive(message? :any) :void {
        console.log(`${this.constructor.name}: ${message}That's just right for me)}}// Player 3
class Fifaer3 extends Fifaer {
    constructor() {
        super()}publicreceive(message? :any) :void {
        console.log(`${this.constructor.name}: ${message}That's too cheap for me)}}let shop: Shop = new ConcreteShop();
let f1: Fifaer = new Fifaer1();
let f2: Fifaer = new Fifaer2();
let f3: Fifaer = new Fifaer3();
shop.register('Ronaldo',f1);
shop.register('Messi',f2);
shop.register('Torres',f3);
f1.send('Torres's Peak Card: 1E EP');

// Fifaer1 has a new card: Torres' Peak card: 1E EP
// FIFA 2: Torres' Peak card: 1E EP just right for me
// FIFA 3: Torres' Peak card: 1E EP is too cheap for me
Copy the code

JS version

var Shop = /** @class */ (function () {
    function Shop() {
        / / store
        this.fifaers = {};
    }
    / / register
    Shop.prototype.register = function (name, fifaer) {
        if (this.fifaers[name]) {
            console.error(name + "\u540D\u79F0\u5DF2\u5B58\u5728");
        }
        else {
            this.fifaers[name] = fifaer;
            fifaer.setMedium(this); }};/ / forwarding
    Shop.prototype.relay = function (fifaer, message) {
        var _this = this;
        Object.keys(this.fifaers).forEach(function (name) {
            if (_this.fifaers[name] !== fifaer) {
                _this.fifaers[name].receive(message);
            }
        });
    };
    returnShop; } ());// Abstract player class
var Fifaer = /** @class */ (function () {
    function Fifaer() {
    }
    Fifaer.prototype.setMedium = function (mediator) {
        this.mediator = mediator;
    };
    Fifaer.prototype.receive = function (message) {
        console.log(this.constructor.name + "Request received:", message);
    };
    Fifaer.prototype.send = function (message) {
        console.log(this.constructor.name + "New Cards on sale:", message);
        this.mediator.relay(this, message); // Ask the intermediary to forward it
    };
    returnFifaer; } ());// The specific intermediary
var ConcreteShop = /** @class */ (function (_super) {
    __extends(ConcreteShop, _super);
    function ConcreteShop() {
        return _super.call(this) | |this;
    }
    return ConcreteShop;
}(Shop));
// Player 1
var Fifaer1 = /** @class */ (function (_super) {
    __extends(Fifaer1, _super);
    function Fifaer1() {
        return _super.call(this) | |this;
    }
    Fifaer1.prototype.receive = function (message) {
        console.log(message + " \u5BF9\u4E8E\u6211\u6765\u8BF4\u592A\u8D35\u4E86");
    };
    return Fifaer1;
}(Fifaer));
// Player 2
var Fifaer2 = /** @class */ (function (_super) {
    __extends(Fifaer2, _super);
    function Fifaer2() {
        return _super.call(this) | |this;
    }
    Fifaer2.prototype.receive = function (message) {
        console.log(this.constructor.name + ":" + message + " \u5BF9\u4E8E\u6211\u6765\u8BF4\u521A\u521A\u597D");
    };
    return Fifaer2;
}(Fifaer));
// Player 3
var Fifaer3 = /** @class */ (function (_super) {
    __extends(Fifaer3, _super);
    function Fifaer3() {
        return _super.call(this) | |this;
    }
    Fifaer3.prototype.receive = function (message) {
        console.log(this.constructor.name + ":" + message + " \u5BF9\u4E8E\u6211\u6765\u8BF4\u592A\u4FBF\u5B9C\u4E86");
    };
    return Fifaer3;
}(Fifaer));
var shop = new ConcreteShop();
var f1 = new Fifaer1();
var f2 = new Fifaer2();
var f3 = new Fifaer3();
shop.register('Ronaldo', f1);
shop.register('Messi', f2);
shop.register('Torres', f3);
f1.send('Torres's Peak Card: 1E EP');
// Fifaer1 has a new card: Torres' Peak card: 1E EP
// FIFA 2: Torres' Peak card: 1E EP just right for me
// FIFA 3: Torres' Peak card: 1E EP is too cheap for me

Copy the code

Why

  • advantages
    • Reduce dependencies between classes, turning one-to-many dependencies into one-to-one dependencies (where a single player only needs to go to the market to buy something instead of having to go to the other player who has the item)
  • disadvantages
    • Intermediaries can become large and logically complex
  • Application scenario: Multiple objects are decoupled. The criterion is whether there is a network structure in the class diagram

Decorator pattern

What

Definition: a pattern that dynamically adds some responsibility (that is, additional functionality) to an existing object without changing its structure

Explanation: Using the decorator pattern allows you to extend the functionality of the source code without changing it

In Devil May Cry 4, Dante gains a lot of items by beating up the lords, thus unlocking several battle modes, such as Gun God mode

How

  • Dante can only greet when he first comes on stage
  • After defeating each Lord (such as the Lord of fire), you will get the corresponding items, which will lead to different battle modes

TS version

@blademasterDecoration
@gunslingerDecoration
@tricksterDecoration 
@royalGuardDecoration
class Dante {
  sayHi() {
    console.log(`My name is: Dante`)}}// Kensei mode
function blademasterDecoration(target: any){
  target.prototype.blademaster = function(){console.log('I am blademaster! ')}}// The gunnery mode
function gunslingerDecoration(target){
  target.prototype.gunslinger = function(){console.log('I am gunslinger! ')}}// The trickster mode
function tricksterDecoration(target){
  target.prototype.trickster = function(){console.log('I am trickster! ')}}// Royal Guard mode
function royalGuardDecoration(target){
  target.prototype.royalGuard = function(){console.log('I am royalGuard! ')}}let dante: Dante = new Dante();
dante.blademaster();
dante.gunslinger();
dante.trickster();
dante.royalGuard();

// I am blademaster!
// I am gunslinger!
// I am trickster!
// I am royalGuard!
Copy the code

JS version

var Dante = /** @class */ (function () {
    function Dante() {
    }
    Dante.prototype.sayHi = function () {
        console.log("My name is: Dante");
    };
    Dante = __decorate([
        blademasterDecoration,
        gunslingerDecoration,
        tricksterDecoration,
        royalGuardDecoration
    ], Dante);
    returnDante; } ());// Kensei mode
function blademasterDecoration(target) {
    target.prototype.blademaster = function () { console.log('I am blademaster! '); };
}
// The gunnery mode
function gunslingerDecoration(target) {
    target.prototype.gunslinger = function () { console.log('I am gunslinger! '); };
}
// The trickster mode
function tricksterDecoration(target) {
    target.prototype.trickster = function () { console.log('I am trickster! '); };
}
// Royal Guard mode
function royalGuardDecoration(target) {
    target.prototype.royalGuard = function () { console.log('I am royalGuard! '); };
}
var dante = new Dante();
dante.blademaster();
dante.gunslinger();
dante.trickster();
dante.royalGuard();
// I am blademaster!
// I am gunslinger!
// I am trickster!
// I am royalGuard!
Copy the code

Why

  • advantages
    • The ability to extend the functionality of the class more flexibly
  • disadvantages
    • Multi-level decoration nesting, increases the difficulty of understanding the code
  • Application scenario: You want to add new functionality without modifying the native code

Adapter mode

What

Definition: To convert the interface of a class to another interface that the client wants, so that classes that otherwise wouldn’t work together because of interface incompatibility can work together

Explanation: In simple terms is to patch, compatible with some old interface

When Katzke is introduced in Game: LOL, he can release a man called a flying Mantis in the air. Because it was too perverted and incompatible with hero balancing, we patched it. W itself was not much changed except that it was not allowed to release in the air.

How

  • The original mantisWIt can be released through the air and has healing and slowing effects
  • The mantis version cannot be released in the airW

TS version

interface TargetW{
    request(): void;
}
/ / source interface
class OriginW{
    normalRequest(): void{
        console.log('My w can heal, slow down');
    }
    flyRequest(): void{
        console.log('My W can release in the air'); }}class AdapterW extends OriginW implements TargetW{
    constructor() {super(a); } request():void{
        console.log('Cancelled the mechanism for w to release in the air');
        this.normalRequest(); }}let target: TargetW = new AdapterW();
target.request();
// Cancellations the release of w in the air
// My w can heal, slow down
Copy the code

JS version

var OriginW = /** @class */ (function () {
    function OriginW() {
    }
    OriginW.prototype.normalRequest = function () {
        console.log('My w can heal, slow down');
    };
    OriginW.prototype.flyRequest = function () {
        console.log('My W can release in the air');
    };
    returnOriginW; } ());var AdapterW = /** @class */ (function (_super) {
    __extends(AdapterW, _super);
    function AdapterW() {
        return _super.call(this) | |this;
    }
    AdapterW.prototype.request = function () {
        console.log('Cancelled the mechanism for w to release in the air');
        this.normalRequest();
    };
    return AdapterW;
}(OriginW));
var target = new AdapterW();
target.request();
// Cancellations the release of w in the air
// My w can heal, slow down
Copy the code

Why

  • advantages
    • Decoupling the target class from the source interface provides good flexibility and extensibility
  • disadvantages
    • Excessive system complexity occurs as a patch.
  • Application scenario: Compatible with old interfaces

Portfolio model

What

Definition: Sometimes called the partial-whole pattern, it is a pattern of grouping objects into tree-like hierarchies to represent “partial-whole” relationships, allowing users to have consistent access to both individual and composite objects.

Explanation: The relationship between objects in the form of 🌲

Game: The structure of the skill tree in Final Fantasy 13. Each point is a tree trunk and each skill point is a leaf

How

  • Create one or more abilities with a point of direction, such as healing, damage, etc
  • Each point direction adds many, many skill points

TS version

interface SkillTree{
    add(st: SkillTree): void;
    operation(): void;
}

class SkillDirection implements SkillTree{
    private children: SkillTree[] = [];
    constructor(private name: string){}
    add(node: SkillTree): void{
        this.children.push(node);
    }

    operation(): void{
        console.log(` you choose${this.name}Direction of the skill tree ')
        this.children.forEach((x: SkillTree) = > x.operation())
    }
}

class SkillPoint implements SkillTree{
    constructor(private name: string){}

    add(node: SkillTree): void{};

    operation(): void{
        console.log('You have learned skill points${this.name}`)}}let tree1: SkillTree = new SkillDirection('treat');
let tree2: SkillTree = new SkillDirection('harm');
let leaf1: SkillTree = new SkillPoint('All health');
let leaf2: SkillTree = new SkillPoint('Single attack');
tree2.add(leaf2);
tree1.add(tree2);
tree1.add(leaf1);
tree1.operation();
// The tree you choose to heal
// You choose the damage direction of the skill tree
// You have learned the skill point monomer attack
// You have learned the skill points all bonus health
Copy the code

JS version

var SkillDirection = /** @class */ (function () {
    function SkillDirection(name) {
        this.name = name;
        this.children = [];
    }
    SkillDirection.prototype.add = function (node) {
        this.children.push(node);
    };
    SkillDirection.prototype.operation = function () {
        console.log("\u4F60\u9009\u62E9" + this.name + "\u65B9\u5411\u7684\u6280\u80FD\u6811");
        this.children.forEach(function (x) { return x.operation(); });
    };
    returnSkillDirection; } ());var SkillPoint = /** @class */ (function () {
    function SkillPoint(name) {
        this.name = name;
    }
    SkillPoint.prototype.add = function (node) {}; ; SkillPoint.prototype.operation =function () {
        console.log("\u4F60\u5DF2\u5B66\u4E60\u6280\u80FD\u70B9 " + this.name);
    };
    returnSkillPoint; } ());var tree1 = new SkillDirection('treat');
var tree2 = new SkillDirection('harm');
var leaf1 = new SkillPoint('All health');
var leaf2 = new SkillPoint('Single attack');
tree2.add(leaf2);
tree1.add(tree2);
tree1.add(leaf1);
tree1.operation();
// The tree you choose to heal
// You choose the damage direction of the skill tree
// You have learned the skill point monomer attack
// You have learned the skill points all bonus health
Copy the code

Why

  • advantages
    • There is little difference between a local call and a global call, in other words the caller does not have to worry about whether the calling object is a simple or a complex structure
  • disadvantages
    • Making the design more complex. The client needs to spend more time sorting out the hierarchy between classes and the trunk uses the implementation classes directly, limiting the interface
  • Application scenario: The described objects conform to the tree structure, such as a file management system

The state pattern

What

Definition: For stateful objects, to extract complex “judgment logic” into different state objects, allowing the state objects to change their behavior when their internal state changes

Explanation: do not use if – else | switch – case for judgment, but state switch by an incoming state object

Game: A Wolf in a reed name of three states, perfect to kill players

How

  • At each stage, the reeds change state

TS version

interface State{
    change(context: WeiMingYiXin): void;
}

class StateOne implements State{
    change(context: WeiMingYiXin): void{
        console.log('Hiroichiro stage'); }}class StateTwo implements State{
    change(context: WeiMingYiXin): void{
        console.log('Jiansheng Reed name one Stage'); }}class StateThree implements State{
    change(context: WeiMingYiXin): void{
        console.log('Thunder Fa Wang Wei Name one Stage'); }}class WeiMingYiXin{
    constructor(private state: State) {}; setState(state: State):void{
        this.state = state;
    }

    request(): void{
        this.state.change(this); }}let ctx: WeiMingYiXin = new WeiMingYiXin(new StateOne());
ctx.request();
ctx.setState(new StateTwo());
ctx.request();
ctx.setState(new StateThree());
ctx.request();

// The ichiro stage
// Jiansheng Reed name one stage
// Lightning method wang Wei Ming single-minded stage
Copy the code

JS version

var StateOne = /** @class */ (function () {
    function StateOne() {
    }
    StateOne.prototype.change = function (context) {
        console.log('Hiroichiro stage');
    };
    returnStateOne; } ());var StateTwo = /** @class */ (function () {
    function StateTwo() {
    }
    StateTwo.prototype.change = function (context) {
        console.log('Jiansheng Reed name one Stage');
    };
    returnStateTwo; } ());var StateThree = /** @class */ (function () {
    function StateThree() {
    }
    StateThree.prototype.change = function (context) {
        console.log('Thunder Fa Wang Wei Name one Stage');
    };
    returnStateThree; } ());var WeiMingYiXin = /** @class */ (function () {
    function WeiMingYiXin(state) {
        this.state = state; }; WeiMingYiXin.prototype.setState =function (state) {
        this.state = state;
    };
    WeiMingYiXin.prototype.request = function () {
        this.state.change(this);
    };
    returnWeiMingYiXin; } ());var ctx = new WeiMingYiXin(new StateOne());
ctx.request();
ctx.setState(new StateTwo());
ctx.request();
ctx.setState(new StateThree());
ctx.request();
// The ichiro stage
// Jiansheng Reed name one stage
// Lightning method wang Wei Ming single-minded stage
Copy the code

Why

  • advantages
    • You can easily add and switch states
  • disadvantages
    • Added a lot of classes, structure and implementation is more complex
  • Application scenario: The behavior of an object depends on the value of some of its properties, and a change in state will result in a change in behavior

Abstract Factory pattern

What

Definition: a schema structure that provides an interface for an access class to create a set of related or interdependent objects without specifying the specific class of the desired product to obtain different levels of products of the same family

Explanation: Analogous to a real factory, an abstract factory can produce multiple categories of products

Game: In red alert, allied and Soviet air and army factories produce different weapons and soldiers, respectively

How

  • Allied army factories produced Mirage tanks and air force factories produced fighter jets
  • The Soviet army factory produced the rhinoceros tank, the Air force factory produced the airship

TS version

interface AbstractAirForce{
    create(): void;
}
interface AbstractGroundForce{
    create(): void;
}
interface AbstractGroup{
    createAirForce(): AbstractAirForce;
    createGroundForce(): AbstractGroundForce;
}

class MAirForce implements AbstractAirForce{
    create(): void{
        console.log('Fighter has been created')}}class MGroundForce implements AbstractGroundForce{
    create(): void{
        console.log('Phantom Tank has been created')}}class SAirForce implements AbstractAirForce{
    create(): void{
        console.log('Airship has been created')}}class SGroundForce implements AbstractGroundForce{
    create(): void{
        console.log('Rhino Tank has been created')}}class MGroup implements AbstractGroup{
    createAirForce(): AbstractAirForce{
        return new MAirForce();
    }
    createGroundForce(): AbstractGroundForce{
        return newMGroundForce(); }}class SGroup implements AbstractGroup{
    createAirForce(): AbstractAirForce{
        return new SAirForce();
    }
    createGroundForce(): AbstractGroundForce{
        return newSGroundForce(); }}let mGroup: AbstractGroup = new MGroup();
let sGroup: AbstractGroup = new SGroup();
mGroup.createAirForce().create();
mGroup.createGroundForce().create();
sGroup.createGroundForce().create()
sGroup.createGroundForce().create();
// Fighter has been created
// The Phantom Tank has been created
// Rhino tank has been created
// Rhino tank has been created
Copy the code

JS version

var MAirForce = /** @class */ (function () {
    function MAirForce() {
    }
    MAirForce.prototype.create = function () {
        console.log('Fighter has been created');
    };
    returnMAirForce; } ());var MGroundForce = /** @class */ (function () {
    function MGroundForce() {
    }
    MGroundForce.prototype.create = function () {
        console.log('Phantom Tank has been created');
    };
    returnMGroundForce; } ());var SAirForce = /** @class */ (function () {
    function SAirForce() {
    }
    SAirForce.prototype.create = function () {
        console.log('Airship has been created');
    };
    returnSAirForce; } ());var SGroundForce = /** @class */ (function () {
    function SGroundForce() {
    }
    SGroundForce.prototype.create = function () {
        console.log('Rhino Tank has been created');
    };
    returnSGroundForce; } ());var MGroup = /** @class */ (function () {
    function MGroup() {
    }
    MGroup.prototype.createAirForce = function () {
        return new MAirForce();
    };
    MGroup.prototype.createGroundForce = function () {
        return new MGroundForce();
    };
    returnMGroup; } ());var SGroup = /** @class */ (function () {
    function SGroup() {
    }
    SGroup.prototype.createAirForce = function () {
        return new SAirForce();
    };
    SGroup.prototype.createGroundForce = function () {
        return new SGroundForce();
    };
    returnSGroup; } ());var mGroup = new MGroup();
var sGroup = new SGroup();
mGroup.createAirForce().create();
mGroup.createGroundForce().create();
sGroup.createGroundForce().create();
sGroup.createGroundForce().create();
// Fighter has been created
// The Phantom Tank has been created
// Rhino tank has been created
// Rhino tank has been created
Copy the code

Why

  • advantages
    • It is convenient to add new specific factories and product families without modifying existing systems, complying with the “open closed principle”
  • disadvantages
    • Adding a new product-level structure is complex, requiring changes to the abstract factory and all concrete factory classes
  • Application scenario: The abstract factory pattern can be used when the objects to be created are a series of interrelated or interdependent product families

Guidelines to follow in a design pattern

Single Responsibility Principle (SRP)

What

An object (method) does only one thing.

How

  • Not all coupling objects should be separated, and if two responsibilities always change at the same time, then they need not be separated
  • There is no need to separate two changed objects if there is no sign of change
  • From the above two points, it can be seen that the coupling object should satisfy the trend of change and the change of the two is not synchronous, then the separation of responsibilities should be considered

Why

  • Reduces the complexity of a single class or object
  • Facilitates code reuse and unit testing
  • When one responsibility needs to change, the other responsibilities will not be affected
  • But this increases the complexity of writing code and makes it harder for objects to relate to each other. How to balance the reusability of code with the difficulty of development is one of the difficulties of this principle

Principle of Least Knowledge (LKP)

What

When designing programs, you should reduce the interaction between objects to avoid a.foo1(b).foo2(c).foo3(d)

If two objects do not have to communicate directly with each other, then the two objects do not have to communicate directly with each other. Instead, a third party object is introduced to take over the communication between these objects

How

  • For example, with the mediator pattern, two objects are not connected directly, but through the mediator’s interface. Just like e-commerce sites, consumers and merchants trade through the platform rather than directly

Why

  • Code logic more clear, avoid miscellaneous chain call
  • Objects are independent of each other and communicate with each other through a third party. This is good for testing and scalability

Open-closed Principle (OCP)

What

When adding new features, you can add code, but you are not allowed to change the source code of the program

How

  • Avoid using large amountsif-else | switch-caseAnd many other conditional branch statements
  • Keep what’s the same, and then encapsulate what’s changed

Why

  • Modifying source code is an operation with a high base of difficulty, and it is not uncommon for one to be modifiedbugAnd introduce morebugAdding code is a smarter option
  • Overall code regression tests are performed after each code change, whereas new modules only need to test the changes
  • Improve code reusability

reference

JS Design Patterns and Development Practices

DesignPatterns_TypeScript