ES6 類聊 JavaScript 設計模式之行為型模式(一)

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

在軟件工程中, 行為型模式為設計模式的一種類型,用來識別對象之間的常用交流模式并加以實現。如此,可在進行這些交流活動時增強彈性。—— 維基百科

  • 責任鏈模式
  • 命令模式
  • 迭代器模式
  • 中介者模式
  • 備忘錄模式

13. 責任鏈模式

責任鏈模式創建了一個對象鏈,從一個點開始,它會停止,直到找到某個條件。

為解除請求的發送者和接收者之間耦合,而使多個對象都有機會處理這個請求。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它。—— 維基百科

實例

實例將使用一個生物攻擊防御的游戲。游戲中生物將增加它的防御和攻擊當它到達一定的點。它會形成一個鏈條,攻擊和防御會增加和減少。

class Creature {
    constructor(name, attack, defense) {
        this.name = name;
        this.attack = attack;
        this.defense = defense;
    }

    toString() {
        return ```{this.name} (攻擊值:``{this.attack} / 防御值:${this.defense})`;
    }
}
class CreatureModifier {
    constructor(creature) {
        this.creature = creature;
        this.next = null;
    }

    add(modifier) {
        if (this.next) {
            this.next.add(modifier);
        } else {
            this.next = modifier;
        }
    }

    handle() {
        if (this.next) {
            this.next.handle();
        }
    }
}

// 增加攻擊邏輯
class DoubleAttackModifier extends CreatureModifier {
    constructor(creature) {
        super(creature);
    }

    handle() {
        console.log(`來自 ${this.creature.name} 的雙重攻擊!`);
        this.creature.attack *= 2;
        super.handle();
    }
}

// 增加防御邏輯
class IncreaseDefenseModifier extends CreatureModifier {
    constructor(creature) {
        super(creature);
    }

    handle() {
        if (this.creature.attack <= 2) {
            console.log(`增加 ${this.creature.name} 的防御!`);
            this.creature.defense++;
        }
        super.handle();
    }
}

接下來看下如何使用責任鏈模式定義的類。

const bacteria = new Creature("Bacteria", 1, 1); 
console.log(bacteria.toString()); // Bacteria (攻擊值:1 / 防御值:1)
const root = new CreatureModifier(bacteria);
root.add(new DoubleAttackModifier(bacteria));
root.add(new IncreaseDefenseModifier(bacteria));
root.handle(); // 來自 Bacteria 的雙重攻擊! // 增加 Bacteria 的防御!
console.log(bacteria.toString()); // Bacteria (攻擊值:2 / 防御值:2)

14. 命令模式

命令模式將創建將動作封裝在對象中的對象。

在面向對象的編程中,命令模式是一種行為設計模式,其中對象用于封裝執行操作或稍后觸發事件所需的所有信息。這些信息包括方法名稱、擁有該方法的對象和方法參數的值。 —— 維基百科

實例

代碼實例將舉一個銀行帳戶的需求,假如必須存入或提取一定數量的錢,會在其中發出命令。

class BankAccount {
    constructor(balance = 0) {
        this.balance = balance;
    }

    deposit(amount) {
        this.balance += amount;
        console.log(`賬戶存入:``{amount},余額為:``{this.balance}`);
    }

    withdraw(amount) {
        if (this.balance - amount >= BankAccount.overdraftLimit) {
            this.balance -= amount;
            console.log(`賬戶提現:``{amount},余額為:``{this.balance}`);
        }
    }

    toString() {
        return `余額為 ${this.balance}`;
    }
}

BankAccount.overdraftLimit = -100;  // 假設可以透支 100

接下來創建相應的命令:

const Action = Object.freeze({
    deposit: 1,
    withdraw: 2,
});

class BankAccountCommand {
    constructor(account, action, amount) {
        this.account = account;
        this.action = action;
        this.amount = amount;
    }

    call() {
        const callHandler = {};
        callHandler[Action.deposit] = (amount) => this.account.deposit(amount);
        callHandler[Action.withdraw] = (amount) =>
            this.account.withdraw(amount);
        callHandler[this.action](this.amount);
    }

    undo() {
        const callHandler = {};
        callHandler[Action.deposit] = (amount) => this.account.withdraw(amount);
        callHandler[Action.withdraw] = (amount) => this.account.deposit(amount);
        callHandler[this.action](this.amount);
    }
}

// 
const bankAccount = new BankAccount(100);
const cmd = new BankAccountCommand(bankAccount, Action.deposit, 50);
cmd.call(); // 賬戶存入:50,余額為:150

console.log(bankAccount.toString()); // 余額為 150

cmd.undo(); // 賬戶提現:50,余額為:100
console.log(bankAccount.toString()); // 余額為 100

15. 迭代器模式

迭代器模式訪問對象的元素而不暴露其底層表示。

在面向對象編程中,迭代器模式是一種設計模式,其中迭代器用于遍歷容器并訪問容器的元素。 —— 維基百科

實例

將舉一個數組的例子,在其中打印一個數組的值,然后使用迭代器將其值對調輸出。

class Stuff {
    constructor() {
        this.a = 33;
        this.b = 66;
    }

    [Symbol.iterator]() {
        let i = 0;
        return {
            next: () => {
                return {
                    done: i > 1,
                    value: this[i++ === 0 ? "a" : "b"],
                };
            },
        };
    }

    get backwards() {
        let i = 0;
        return {
            next: () => {
                return {
                    done: i > 1,
                    value: this[i++ === 0 ? "b" : "a"],
                };
            },
            [Symbol.iterator]: function () {
                return this;
            },
        };
    }
}

const values = [100, 200, 300];
for (const i in values) {
    console.log(`索引 ``{i} 的值 ``{values[i]}`);
}

for (const v in values) {
    console.log(`值為 ${v}`);
}

const stuff = new Stuff();
console.log("=====>正常打印<======");
for (const item of stuff) {
    console.log(item);
}
console.log("=====>對調打印<======");
for (const item of stuff.backwards) {
    console.log(item);
}

運行后在終端顯示結果如下:

索引 0 的值 100
索引 1 的值 200
索引 2 的值 300
值為 0
值為 1
值為 2
=====>正常打印<======
33
66
=====>對調打印<======
66
33

16. 中介者模式

中介者模式添加了一個第三方對象來控制兩個對象之間的交互。它允許類之間的松散耦合,因為它是唯一詳細了解其方法的類。

中介者模式定義了一個對象,該對象封裝了一組對象如何交互。這種模式被認為是一種行為模式,因為它可以改變程序的運行行為。在面向對象編程中,程序通常由許多類組成。—— 維基百科

實例

代碼是一個人使用聊天室的例子。在這里,聊天室充當了兩個人交流的中介。

class Person {
    constructor(name) {
        this.name = name;
        this.chatLog = [];
    }

    receive(sender, message) {
        const s = ```{sender}:“``{message}”`;
        console.log(`【``{this.name}的聊天會話】``{s}`);
        this.chatLog.push(s);
    }

    say(message) {
        this.room.broadcast(this.name, message);
    }

    pm(who, message) {
        this.room.message(this.name, who, message);
    }
}

// 創建聊天室
class ChatRoom {
    constructor() {
        this.people = [];
    }

    broadcast(source, message) {
        for (const p of this.people) {
            if (p.name !== source) {
                p.receive(source, message);
            }
        }
    }

    join(p) {
        const joinMsg = `${p.name} 加入聊天室`;
        this.broadcast("room", joinMsg);
        p.room = this;
        this.people.push(p);
    }

    message(source, destination, message) {
        for (const p of this.people) {
            if (p.name === destination) {
                p.receive(source, message);
            }
        }
    }
}

// 使用方式
const room = new ChatRoom();
const Quintion = new Person("Quintion");
const Devpoint = new Person("Devpoint");
room.join(Quintion);
room.join(Devpoint);
Quintion.say("下午好!");

const Tony = new Person("Tony");
room.join(Tony);
Tony.say("大家好!");

運行后在終端可以看到如下結果:

【Quintion的聊天會話】room:“Devpoint 加入聊天室”
【Devpoint的聊天會話】Quintion:“下午好!”
【Quintion的聊天會話】room:“Tony 加入聊天室”
【Devpoint的聊天會話】room:“Tony 加入聊天室”
【Quintion的聊天會話】Tony:“大家好!”
【Devpoint的聊天會話】Tony:“大家好!”

17. 備忘錄模式:Memento

備忘錄模式將對象恢復到之前的狀態。

備忘錄模式是一種軟件設計模式,它提供了將對象恢復到其先前狀態的能力。備忘錄模式由三個對象實現:發起者、看守者和備忘錄。使用的場景是在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可將該對象恢復到原先保存的狀態。

實例

將以一個銀行賬戶為例,其中存儲之前的狀態,并具有撤銷功能。

// 備忘錄
class Memento {
    constructor(balance) {
        this.balance = balance;
    }

    getBalance() {
        return this.balance;
    }
}

class BankAccount {
    constructor(balance = 0) {
        this.balance = balance;
    }

    deposit(amount) {
        this.balance += amount;
        return new Memento(this.balance);
    }

    restore(m) {
        this.balance = m.balance;
    }

    toString() {
        return `賬戶余額:${this.balance}`;
    }
}

// 這里開始如何使用上面定義的類
const bankAccount = new BankAccount(1000);
const m1 = bankAccount.deposit(200);
console.log(bankAccount.toString()); // 當前賬戶余額為:1200
const m2 = bankAccount.deposit(200);
console.log(bankAccount.toString()); // 當前賬戶余額為:1400

// 恢復到m1
bankAccount.restore(m1);
console.log(bankAccount.toString()); // 賬戶余額:1200

// 恢復到m2
bankAccount.restore(m2);
console.log(bankAccount.toString()); // 賬戶余額:1400

寫在最后

本文總結了行為型設計模式中的一部份,后面還將繼續學習總結剩下的5種設計模式。