ES6 類聊 JavaScript 設(shè)計(jì)模式之行為型模式(二)

本文是《ES6 類聊 JavaScript 設(shè)計(jì)模式》的第四篇,介紹第三種類型的設(shè)計(jì)模式行為設(shè)計(jì)模式,其特別關(guān)注對(duì)象之間的通信。

在軟件工程中, 行為型模式為設(shè)計(jì)模式的一種類型,用來(lái)識(shí)別對(duì)象之間的常用交流模式并加以實(shí)現(xiàn)。如此,可在進(jìn)行這些交流活動(dòng)時(shí)增強(qiáng)彈性。—— 維基百科

  • 觀察者模式:Observer
  • 訪問(wèn)者模式:Visitor
  • 策略模式:Strategy
  • 狀態(tài)模式:State
  • 模板方法模式:Template Method

18. 觀察者模式:Observer

觀察者模式是一種關(guān)鍵的行為設(shè)計(jì)模式,它定義了對(duì)象之間的一對(duì)多依賴關(guān)系,以便當(dāng)一個(gè)對(duì)象(發(fā)布者)更改其狀態(tài)時(shí),所有其他依賴對(duì)象(訂閱者)都會(huì)收到通知并自動(dòng)更新。也稱為 PubSub(發(fā)布者/訂閱者)或事件調(diào)度程序/偵聽(tīng)器模式。發(fā)布者有時(shí)稱為主體,訂閱者有時(shí)稱為觀察者。

觀察者模式是一種軟件設(shè)計(jì)模式,其中一個(gè)名為主體的對(duì)象維護(hù)其依賴項(xiàng)列表,稱為觀察者,并自動(dòng)通知他們?nèi)魏螤顟B(tài)更改,通常通過(guò)調(diào)用他們的方法之一。 —— 維基百科

實(shí)例

將創(chuàng)建了一個(gè)簡(jiǎn)單的 Subject 類,它具有從訂閱者集合中添加和刪除 Observer 類對(duì)象的方法。將 Subject 類對(duì)象中的任何更改傳播到訂閱的觀察者的方法 fire

class Subject {
    constructor() {
        this._observers = [];
    }

    subscribe(observer) {
        this._observers.push(observer);
    }

    unsubscribe(observer) {
        this._observers = this._observers.filter((obs) => observer !== obs);
    }

    fire(change) {
        this._observers.forEach((observer) => {
            observer.update(change);
        });
    }
}

class Observer {
    constructor(state) {
        this.state = state;
        this.initialState = state;
    }

    update(change) {
        const state = this.state;
        const handlers = {
            inc: (num) => ++num,
            dec: (num) => --num,
        };
        const changeMethod = handlers[change.toLowerCase()];
        this.state = changeMethod ? changeMethod(state) : this.initialState;
    }
}

// 使用
const sub = new Subject();

const obs1 = new Observer(1);
const obs2 = new Observer(19);

sub.subscribe(obs1);
sub.subscribe(obs2);

sub.fire("INC");

console.log(obs1.state); // 2
console.log(obs2.state); // 20

19. 訪問(wèn)者模式:Visitor

訪問(wèn)者模式向?qū)ο筇砑硬僮鞫鵁o(wú)需修改它們。

訪問(wèn)者模式是一種將算法與其操作的對(duì)象結(jié)構(gòu)分離的方法。這種分離的實(shí)際結(jié)果是能夠在不修改結(jié)構(gòu)的情況下向現(xiàn)有對(duì)象結(jié)構(gòu)添加新操作。—— 維基百科

實(shí)例

將舉一個(gè)數(shù)學(xué)表達(dá)式 NumberExpression 的例子,列出給定的算術(shù)表達(dá)式。

class NumberExpression {
    constructor(value) {
        this.value = value;
    }

    print(buffer) {
        buffer.push(this.value.toString());
    }
}

class AdditionExpression {
    constructor(left, right) {
        this.left = left;
        this.right = right;
    }

    print(buffer) {
        buffer.push("(");
        this.left.print(buffer);
        buffer.push("+");
        this.right.print(buffer);
        buffer.push(")");
    }
}

使用方式如下:

const e = new AdditionExpression(
    new NumberExpression(6),
    new AdditionExpression(new NumberExpression(2), new NumberExpression(8))
);

const buffer = [];
e.print(buffer);
console.log(buffer.join(""));

輸出結(jié)果如下:

(6+(2+8))

20. 策略模式:Strategy

策略模式允許在某些情況下選擇其中一種算法。允許為特定任務(wù)封裝替代算法。它定義了一系列算法并以這樣一種方式封裝它們,即它們?cè)谶\(yùn)行時(shí)可互換,而無(wú)需客戶干預(yù)或知識(shí)。

策略模式是一種行為軟件設(shè)計(jì)模式,可以在運(yùn)行時(shí)選擇算法。代碼不是直接實(shí)現(xiàn)單個(gè)算法,而是接收運(yùn)行時(shí)指令,以確定要使用一系列算法中的哪一個(gè)。 —— 維基百科

實(shí)例

將舉一個(gè)例子,有一個(gè)文本處理器,將根據(jù)策略(HTML 或 Markdown)輸出列表數(shù)據(jù)格式。

const OutputFormat = Object.freeze({
    markdown: 0,
    html: 1,
});

class ListStrategy {
    start(buffer) {}
    end(buffer) {}
    addListItem(buffer, item) {}
}

class MarkdownListStrategy extends ListStrategy {
    addListItem(buffer, item) {
        buffer.push(` * ${item}`);
    }
}

class HtmlListStrategy extends ListStrategy {
    start(buffer) {
        buffer.push("<ul>");
    }
    end(buffer) {
        buffer.push("</ul>");
    }
    addListItem(buffer, item) {
        buffer.push(`   <li>${item}</li>`);
    }
}

創(chuàng)建 TextProcessor 類

class TextProcessor {
    constructor(outputFormat) {
        this.buffer = [];
        this.setOutputFormat(outputFormat);
    }

    setOutputFormat(format) {
        switch (format) {
            case OutputFormat.markdown:
                this.listStrategy = new MarkdownListStrategy();
                break;
            case OutputFormat.html:
                this.listStrategy = new HtmlListStrategy();
                break;
        }
    }

    appendList(items) {
        this.listStrategy.start(this.buffer);
        for (const item of items) {
            this.listStrategy.addListItem(this.buffer, item);
        }
        this.listStrategy.end(this.buffer);
    }

    clear() {
        this.buffer = [];
    }

    toString() {
        return this.buffer.join("\n");
    }
}

下面是使用方式:

console.log("==============Markdown===============")
const tp = new TextProcessor();
const arrayItems = ["第一條", "第二條", "第三條"];
tp.setOutputFormat(OutputFormat.markdown);
tp.appendList(arrayItems);
console.log(tp.toString());

console.log("==============HTML===============")
tp.clear();
tp.setOutputFormat(OutputFormat.html);
tp.appendList(arrayItems);
console.log(tp.toString());

輸出結(jié)果如下:

==============Markdown===============
 * 第一條
 * 第二條
 * 第三條
==============HTML===============
<ul>
   <li>第一條</li>
   <li>第二條</li>
   <li>第三條</li>
</ul>

21. 狀態(tài)模式:State

狀態(tài)模式允許對(duì)象根據(jù)其內(nèi)部狀態(tài)的變化來(lái)改變其行為。狀態(tài)模式類返回的對(duì)象似乎改變了它的類。它為一組有限的對(duì)象提供特定于狀態(tài)的邏輯,其中每個(gè)對(duì)象類型代表一個(gè)特定的狀態(tài)。

狀態(tài)模式是一種行為軟件設(shè)計(jì)模式,它允許對(duì)象在其內(nèi)部狀態(tài)發(fā)生變化時(shí)改變其行為。這種模式接近于有限狀態(tài)機(jī)的概念。 —— 維基百科

實(shí)例

將舉一個(gè)電燈開(kāi)關(guān)的例子,打開(kāi)或關(guān)閉開(kāi)關(guān),它的狀態(tài)就會(huì)改變。

class State {
    constructor() {
        if (this.constructor === State) throw new Error("abstract!");
    }

    on(sw) {
        console.log("燈已打開(kāi)!");
    }

    off(sw) {
        console.log("燈已關(guān)閉!");
    }
}

class OffState extends State {
    constructor() {
        super();
        console.log("關(guān)燈");
    }

    on(sw) {
        console.log("開(kāi)燈中…");
        sw.state = new OnState();
    }
}

class OnState extends State {
    constructor() {
        super();
        console.log("開(kāi)燈");
    }

    off(sw) {
        console.log("關(guān)燈中…");
        sw.state = new OffState();
    }
}

class Switch {
    constructor() {
        this.state = new OffState();
    }

    on() {
        this.state.on(this);
    }

    off() {
        this.state.off(this);
    }
}

下面就是使用方法:

const switchHelper = new Switch();
switchHelper.on();
switchHelper.off();

將在控制臺(tái)上可以看到日志:

關(guān)燈
開(kāi)燈中…
開(kāi)燈
關(guān)燈中…
關(guān)燈

22. 模板方法模式:Template Method

模板方法模式是一種基于定義算法骨架或操作實(shí)現(xiàn)的行為設(shè)計(jì)模式,但將一些步驟推遲到子類。它允許子類重新定義算法的某些步驟,而不改變算法的外部結(jié)構(gòu)。

模板方法是超類中的一個(gè)方法,通常是一個(gè)抽象超類,并根據(jù)許多高級(jí)步驟定義了操作的框架。 —— 維基百科

實(shí)例

將以國(guó)際象棋游戲?yàn)槔?/p>

class Game {
    constructor(numberOfPlayers) {
        this.numberOfPlayers = numberOfPlayers;
        this.currentPlayer = 0;
    }

    run() {
        this.start();
        while (!this.haveWinner) {
            this.takeTurn();
        }
        console.log(`玩家【${this.winningPlayer}】贏了!`);
    }
    start() {}
    get haveWinner() {}
    takeTurn() {}
    get winningPlayer() {}
}

接下來(lái)創(chuàng)建繼承上面 Game 類的國(guó)際象棋 Class。上面的 Game 類就是一個(gè)游戲的骨架,下面的 Chess 類就是基于這個(gè)骨架來(lái)實(shí)現(xiàn)其方法。

class Chess extends Game {
    constructor() {
        super(2);
        this.maxTurns = 10;
        this.turn = 1;
    }

    start() {
        console.log(` ${this.numberOfPlayers} 玩家開(kāi)始國(guó)際象棋游戲`);
    }

    get haveWinner() {
        return this.turn === this.maxTurns;
    }

    takeTurn() {
        console.log(
            `Turn ``{this.turn++} taken by player ``{this.currentPlayer}`
        );
        this.currentPlayer = (this.currentPlayer + 1) % this.numberOfPlayers;
    }

    get winningPlayer() {
        return this.currentPlayer;
    }
}

使用的方式如下:

const chess = new Chess();
chess.run();

總結(jié)

設(shè)計(jì)模式對(duì)軟件工程至關(guān)重要,并且對(duì)于解決常見(jiàn)問(wèn)題非常有幫助。通過(guò)這系列文章,加強(qiáng)對(duì)設(shè)計(jì)模式的理解。