天天看點

【面試題】 ES6 類聊 JavaScript 設計模式之行為型模式(二)

【面試題】 ES6 類聊 JavaScript 設計模式之行為型模式(二)

本文是《ES6 類聊 JavaScript 設計模式》的第四篇,介紹第三種類型的設計模式行為設計模式,其特别關注對象之間的通信。

在軟體工程中, 行為型模式為設計模式的一種類型,用來識别對象之間的常用交流模式并加以實作。如此,可在進行這些交流活動時增強彈性。—— 維基百科
  • 觀察者模式:Observer
  • 通路者模式:Visitor
  • 政策模式:Strategy
  • 狀态模式:State
  • 模闆方法模式:Template Method
  • 《​​ES6 類聊 JavaScript 設計模式之建立型模式​​》

18. 觀察者模式:Observer

觀察者模式是一種關鍵的行為設計模式,它定義了對象之間的一對多依賴關系,以便當一個對象(釋出者)更改其狀态時,所有其他依賴對象(訂閱者)都會收到通知并自動更新。也稱為 ​

​PubSub​

​(釋出者/訂閱者)或事件排程程式/偵聽器模式。釋出者有時稱為主體,訂閱者有時稱為觀察者。

觀察者模式是一種軟體設計模式,其中一個名為主體的對象維護其依賴項清單,稱為觀察者,并自動通知他們任何狀态更改,通常通過調用他們的方法之一。 —— 維基百科

​​執行個體​​

将建立了一個簡單的 Subject 類,它具有從訂閱者集合中添加和删除 Observer 類對象的方法。将 Subject 類對象中的任何更改傳播到訂閱的觀察者的方法 ​

​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. 通路者模式:Visitor

通路者模式向對象添加操作而無需修改它們。

通路者模式是一種将算法與其操作的對象結構分離的方法。這種分離的實際結果是能夠在不修改結構的情況下向現有對象結構添加新操作。—— 維基百科

執行個體

将舉一個數學表達式 ​

​NumberExpression​

​ 的例子,列出給定的算術表達式。

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(""));
複制代碼      

輸出結果如下:

(6+(2+8))
複制代碼      

20. 政策模式:Strategy

政策模式允許在某些情況下選擇其中一種算法。允許為特定任務封裝替代算法。它定義了一系列算法并以這樣一種方式封裝它們,即它們在運作時可互換,而無需客戶幹預或知識。

政策模式是一種行為軟體設計模式,可以在運作時選擇算法。代碼不是直接實作單個算法,而是接收運作時指令,以确定要使用一系列算法中的哪一個。 —— 維基百科

執行個體

将舉一個例子,有一個文本處理器,将根據政策(​

​HTML​

​​ 或 ​

​Markdown​

​)輸出清單資料格式。

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>`);
    }
}
複制代碼      

建立 ​

​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());
複制代碼      

輸出結果如下:

==============Markdown===============
 * 第一條
 * 第二條
 * 第三條
==============HTML===============
<ul>
   <li>第一條</li>
   <li>第二條</li>
   <li>第三條</li>
</ul>
複制代碼      

21. 狀态模式:State

狀态模式允許對象根據其内部狀态的變化來改變其行為。狀态模式類傳回的對象似乎改變了它的類。它為一組有限的對象提供特定于狀态的邏輯,其中每個對象類型代表一個特定的狀态。

狀态模式是一種行為軟體設計模式,它允許對象在其内部狀态發生變化時改變其行為。這種模式接近于有限狀态機的概念。 —— 維基百科

執行個體

将舉一個電燈開關的例子,打開或關閉開關,它的狀态就會改變。

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

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

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

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

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

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

    off(sw) {
        console.log("關燈中…");
        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();
複制代碼      

将在控制台上可以看到日志:

關燈
開燈中…
開燈
關燈中…
關燈
複制代碼      

22. 模闆方法模式:Template Method

模闆方法模式是一種基于定義算法骨架或操作實作的行為設計模式,但将一些步驟推遲到子類。它允許子類重新定義算法的某些步驟,而不改變算法的外部結構。

模闆方法是超類中的一個方法,通常是一個抽象超類,并根據許多進階步驟定義了操作的架構。 —— 維基百科

執行個體

将以國際象棋遊戲為例。

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() {}
}
複制代碼      

接下來建立繼承上面 ​

​Game​

​​ 類的國際象棋 ​

​Class​

​。上面的 Game 類就是一個遊戲的骨架,下面的 Chess 類就是基于這個骨架來實作其方法。

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

    start() {
        console.log(` ${this.numberOfPlayers} 玩家開始國際象棋遊戲`);
    }

    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();
複制代碼      

​​總結​​

給大家推薦一個實用面試題庫

1、前端面試題庫 (面試必備)        

繼續閱讀