以遊戲玩家的視角開啓設計模式

前言

最近學習設計模式和TypeScript,發現網上的資料略顯硬核,不太容易理解記憶,常常看完就忘。做爲一名遊戲玩家,發現遊戲中的不少場景都能和相應的設計模式相關聯,不只便於理解,更利於合理地運用設計模式。因爲我的水平有限,只整理我的以爲比較有趣的設計模式,每一個模式採用哲學三問進行講解。若是對你有幫助的話,歡迎點贊和收藏💖,圖片源自網絡,侵刪。node

目錄

Snipaste_2020-06-07_17-36-43.png

設計模式

單例模式

What

定義:一個類僅有一個實例,並提供一個訪問它的全局訪問點git

解釋:實質上就是起到一個存儲全局變量的做用,其餘的類和模塊只能經過該類提供的接口修改其惟一實例github

Game:在DNF一塊兒組團刷本的日子中,副本BOSS看做爲一個單例,玩家能夠經過各類技能或者平A去消耗BOSS算法

而且該本中的全部玩家都是對同一個BOSS形成傷害設計模式

image

How

  • BOSS爲單例,只會被實例化一次
  • 不一樣玩家使用不一樣攻擊方式對BOSS形成傷害
  • BOSS受到的傷害爲玩家們形成傷害的總和

TS版本網絡

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{
        // 若是已經實例化了那麼直接返回
        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(`我是一名${this.name},打出了${harm}傷害,BOSS還剩${boss.getHp()}血`)
    }
}

let p1: Player = new Player('鬼泣');
let p2: Player = new Player('街霸');
let p3: Player = new Player('阿修羅');
// 對同一個boss瘋狂輸出
p1.attack(100,Boss.getInstance());
p2.attack(80,Boss.getInstance());
p3.attack(120,Boss.getInstance());

// 我是一名鬼泣,打出了100傷害,BOSS還剩900血
// 我是一名街霸,打出了80傷害,BOSS還剩820血
// 我是一名阿修羅,打出了120傷害,BOSS還剩700血
複製代碼

JS版本dom

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 () {
        // 若是已經實例化了那麼直接返回
        if (!this.instance) {
            this.instance = new Boss();
        }
        return this.instance;
    };
    Boss.instance = null;
    return Boss;
}());
var Player = /** @class */ (function () {
    function Player(name) {
        this.name = name;
    }
    Player.prototype.attack = function (harm, boss) {
        boss.getInjured(harm);
        console.log("我是一名" + this.name + ",打出了" + harm + "傷害,BOSS還剩" + boss.getHp() + "血");
    };
    return Player;
}());
var p1 = new Player('鬼泣');
var p2 = new Player('街霸');
var p3 = new Player('阿修羅');
// 對同一個boss瘋狂輸出
p1.attack(100, Boss.getInstance());
p2.attack(80, Boss.getInstance());
p3.attack(120, Boss.getInstance());

// 我是一名鬼泣,打出了100傷害,BOSS還剩900血
// 我是一名街霸,打出了80傷害,BOSS還剩820血
// 我是一名阿修羅,打出了120傷害,BOSS還剩700血
複製代碼

Why

  • 優勢
    • 單例模式提供了對共享資源訪問的惟一接口,避免了對共享資源的多重佔用
    • 單例模式只會實例化一次對象,節約內存開銷
  • 缺點
    • 濫用單例模式就跟濫用全局變量同樣,未使用的引用對象會被GC自動回收
    • 單例模式中複雜的功能可能會違背單一職責原則
  • 應用場景:針對一些靜態的共享資源,能夠採用單例模式對其進行訪問。今後處能夠看出舉的遊戲例子自己不太適合用單例模式(由於它是一個頻繁變更的對象)

策略模式

What

定義:策略模式指的是定義一系列的算法,把它們一個個封裝起來。將不變的部分和變化的部分隔開是每一個設計模式的主題,策性能

略模式也不例外,策略模式的目的就是將算法的使用與算法的實現分離開來。單元測試

解釋:常言道條條大路通羅馬,所謂策略模式也就是爲實現某種功能而採起不一樣的策略算法學習

Game:在爐石傳說遊戲中,盜賊的兩套經典卡牌奇蹟賊爆牌賊,雖然都是盜賊玩家取勝的法寶,但這兩套牌的取勝之道卻大相徑庭。奇蹟賊依靠一回合的極限操做每每能轉危爲安,讓對面忽然去世;而爆牌賊則是以疲勞和爆對面key牌的運營方式取勝。

image

How

  • 奇蹟賊爆牌賊都是盜賊的卡組
  • 奇蹟賊靠一回合的極限操做
  • 爆牌賊靠長時間的運營

TS版本

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

class MiracleDeck implements Deck{
    name: string = '奇蹟賊';
    kill(){
        console.log(`${this.name}:十七張牌就是能秒你`);
    }
}

class ExplosiveDeck implements Deck{
    name: string = '爆牌賊';
    kill(){
        console.log(`${this.name}:我要爆光你的牌庫!`)
    }
}

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();

// 奇蹟賊:十七張牌就是能秒你
// 爆牌賊:我要爆光你的牌庫!
複製代碼

JS版本

var MiracleDeck = /** @class */ (function () {
    function MiracleDeck() {
        this.name = '奇蹟賊';
    }
    MiracleDeck.prototype.kill = function () {
        console.log(this.name + "十七張牌就是能秒你");
    };
    return MiracleDeck;
}());
var ExplosiveDeck = /** @class */ (function () {
    function ExplosiveDeck() {
        this.name = '爆牌賊';
    }
    ExplosiveDeck.prototype.kill = function () {
        console.log(this.name + "我要爆光你的牌庫!");
    };
    return ExplosiveDeck;
}());
var Robber = /** @class */ (function () {
    function Robber() {
    }
    Robber.prototype.setDeck = function (deck) {
        this.deck = deck;
    };
    Robber.prototype.winTheGame = function () {
        this.deck.kill();
    };
    ;
    return Robber;
}());
var rb = new Robber();
rb.setDeck(new MiracleDeck());
rb.winTheGame();
rb.setDeck(new ExplosiveDeck());
rb.winTheGame();
// 奇蹟賊:十七張牌就是能秒你
// 爆牌賊:我要爆光你的牌庫!
複製代碼

Why

  • 優勢
    • 算法能夠自由切換,可擴展性好
  • 缺點
    • 策略類會增多而且每一個策略類都須要向外暴露
  • 應用場景:多態場景,如分享功能,分享到不一樣平臺的內部實現是不相同的

代理模式

What

定義:爲一個對象提供一個代理者,以便控制對它的訪問

解釋:好比明星和經紀人的關係,經紀人會幫助明星處理商演贊助等細節問題,明星負責簽字就好

Game:做爲一個FM懶人玩家,只想把時間花在看模擬比賽上,其餘不想作的事就很開心地甩給助理教練啦

Snipaste_2020-06-06_18-13-30.png

How

  • 玩家只須要設定戰術,觀看比賽
  • 助理教練負責賽前針對性設置,球隊訓話以及賽後的新聞發佈會

TS版本

interface Match{
    play(): void;
}

class Manager implements Match{
    play(): void{
        console.log('比賽開始了,是由皇家馬德里對陣拜仁慕尼黑。。。')
    }
}

class Cotch implements Match{
    private manager: Manager = new Manager();
    beforePlay(): void{
        console.log('佈置戰術');
        console.log('球隊訓話');
    }
    afterPlay(): void{
        console.log('賽後採訪');
    }
    play(): void{
        this.beforePlay();
        this.manager.play();
        this.afterPlay();
    }
}

let proxy: Cotch = new Cotch();
proxy.play();

// 佈置戰術
// 球隊訓話
// 比賽開始了,是由皇家馬德里對陣拜仁慕尼黑。。。
// 賽後採訪
複製代碼

JS版本

var Manager = /** @class */ (function () {
    function Manager() {
    }
    Manager.prototype.play = function () {
        console.log('比賽開始了,是由皇家馬德里對陣拜仁慕尼黑。。。');
    };
    return Manager;
}());
var Cotch = /** @class */ (function () {
    function Cotch() {
        this.manager = new Manager();
    }
    Cotch.prototype.beforePlay = function () {
        console.log('佈置戰術');
        console.log('球隊訓話');
    };
    Cotch.prototype.afterPlay = function () {
        console.log('賽後採訪');
    };
    Cotch.prototype.play = function () {
        this.beforePlay();
        this.manager.play();
        this.afterPlay();
    };
    return Cotch;
}());
var proxy = new Cotch();
proxy.play();
// 佈置戰術
// 球隊訓話
// 比賽開始了,是由皇家馬德里對陣拜仁慕尼黑。。。
// 賽後採訪
複製代碼

Why

  • 優勢
    • 將真實對象和目標對象分類
    • 下降代碼的耦合度,擴展性好
  • 缺點
    • 類數量增長了,增大了系統複雜度,下降了系統的性能
  • 應用場景:能夠攔截一些請求,如ES6中的Proxy

發佈-訂閱模式

What

定義:對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知

解釋:好比JS中的addEventListener

Game:進遊戲後你會訂閱隊友和敵人的生存狀態,當生存狀態發生變化時,系統會及時通知你

Snipaste_2020-06-06_19-14-46.png

How

  • 玩家須要訂閱隊友和敵人的狀態
  • 狀態須要被觸發
  • 系統會通知你改變後的狀態

TS版本

class EventEmitter{
    private events: Object = {};    // 存儲事件
    private key: number = 0;    // 事件的惟一標識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(`抱歉,你沒有定義 ${name}監聽器`)
        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('你可愛的隊友已被擊殺');
})
player.on('enemyDied',function(): void{
    console.log('打的好呀,小帥哥')
})
// 模擬戰況
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++;
}

// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 你可愛的隊友已被擊殺
// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 你可愛的隊友已被擊殺
// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 打的好呀,小帥哥
複製代碼

JS版本

var EventEmitter = /** @class */ (function () {
    function EventEmitter() {
        this.events = {}; // 存儲事件
        this.key = 0; // 事件的惟一標識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) { return x.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) { return x(); });
        }
    };
    return EventEmitter;
}());
var player = new EventEmitter();
player.on('friendDied', function () {
    console.log('你可愛的隊友已被擊殺');
});
player.on('enemyDied', function () {
    console.log('打的好呀,小帥哥');
});
// 模擬戰況
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++;
}
// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 你可愛的隊友已被擊殺
// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 你可愛的隊友已被擊殺
// 你可愛的隊友已被擊殺
// 打的好呀,小帥哥
// 打的好呀,小帥哥

複製代碼

Why

  • 優勢
    • 很好的實現事件推送,創建一套完備的觸發機制
  • 缺點
    • 訂閱數量過多的話,觸發有必定時延
  • 應用場景JS中的addEventListenerRedux中的數據流模型

中介者模式

What

定義:一箇中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合鬆散,且能夠獨立地改變它們之間的交互。

解釋:簡單來講就是模塊之間通訊的中間商

Game:FIFA Online中,你能夠在交易系統上上架球員卡,而後該商品會被交易系統轉發給其餘玩家

Snipaste_2020-06-06_19-14-46.png

How

  • 玩家1上架球員卡
  • 交易中介系統轉發信息
  • 玩家2,3收到新上架商品的信息

TS版本

abstract class Shop {
    //存儲
    public fifaers: Object = {}
    //註冊
    public register(name: string, fifaer: Fifaer): void {
        if (this.fifaers[name]) {
            console.error(`${name}名稱已存在`);
        } else {
            this.fifaers[name] = fifaer;
            fifaer.setMedium(this);
        }
    }
    //轉發
    public relay(fifaer: Fifaer, message?: any): void {
        Object.keys(this.fifaers).forEach((name: string) => {
            if (this.fifaers[name] !== fifaer) {
                this.fifaers[name].receive(message);
            }
        })
    }
}
//抽象玩家類
abstract class Fifaer {
    protected mediator: Shop;
    public setMedium(mediator: Shop): void {
        this.mediator = mediator;
    }
    public receive(message?: any): void {
        console.log(this.constructor.name + "收到請求:", message);
    }
    public send(message?: any): void {
        console.log(this.constructor.name + "上架新卡:", message);
        this.mediator.relay(this, message); //請中介者轉發
    }
}


//具體中介者
class ConcreteShop extends Shop {
    constructor() {
        super()
    }
}
//具體玩家1
class Fifaer1 extends Fifaer {
    constructor() {
        super()
    }
    public receive(message?: any): void {
        console.log(`${message} 對於我來講太貴了`)
    }
}
//具體玩家2
class Fifaer2 extends Fifaer {
    constructor() {
        super()
    }
    public receive(message?: any): void {
        console.log(`${this.constructor.name}: ${message} 對於我來講剛恰好`)
    }
}
//具體玩家3
class Fifaer3 extends Fifaer {
    constructor() {
        super()
    }
    public receive(message?: any): void {
        console.log(`${this.constructor.name}: ${message} 對於我來講太便宜了`)
    }
}

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('託雷斯的巔峯卡: 1E ep');

// Fifaer1上架新卡: 託雷斯的巔峯卡: 1E ep
// Fifaer2: 託雷斯的巔峯卡: 1E ep 對於我來講剛恰好
// Fifaer3: 託雷斯的巔峯卡: 1E ep 對於我來講太便宜了
複製代碼

JS版本

var Shop = /** @class */ (function () {
    function Shop() {
        //存儲
        this.fifaers = {};
    }
    //註冊
    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);
        }
    };
    //轉發
    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);
            }
        });
    };
    return Shop;
}());
//抽象玩家類
var Fifaer = /** @class */ (function () {
    function Fifaer() {
    }
    Fifaer.prototype.setMedium = function (mediator) {
        this.mediator = mediator;
    };
    Fifaer.prototype.receive = function (message) {
        console.log(this.constructor.name + "收到請求:", message);
    };
    Fifaer.prototype.send = function (message) {
        console.log(this.constructor.name + "上架新卡:", message);
        this.mediator.relay(this, message); //請中介者轉發
    };
    return Fifaer;
}());
//具體中介者
var ConcreteShop = /** @class */ (function (_super) {
    __extends(ConcreteShop, _super);
    function ConcreteShop() {
        return _super.call(this) || this;
    }
    return ConcreteShop;
}(Shop));
//具體玩家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));
//具體玩家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));
//具體玩家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('託雷斯的巔峯卡: 1E ep');
// Fifaer1上架新卡: 託雷斯的巔峯卡: 1E ep
// Fifaer2: 託雷斯的巔峯卡: 1E ep 對於我來講剛恰好
// Fifaer3: 託雷斯的巔峯卡: 1E ep 對於我來講太便宜了

複製代碼

Why

  • 優勢
    • 減小類之間的依賴,將本來一對多的依賴變成一對一的依賴(即單個玩家買東西只需去交易市場而不須要去找持有該物品的玩家)
  • 缺點
    • 中介者可能會變得很大,邏輯複雜
  • 應用場景:多個對象解耦合,判斷標準是類圖中是否存在了網狀結構

裝飾器模式

What

定義:不改變現有對象結構的狀況下,動態地給該對象增長一些職責(即增長其額外功能)的模式

解釋:使用裝飾器模式能在不改變源代碼的基礎上,對源代碼的功能進行拓展

Game:鬼泣4中,但丁在暴揍各大領主得到許多道具,所以解鎖幾種戰鬥模式,如槍神模式

Snipaste_2020-06-07_18-47-50.png

How

  • 但丁剛登場時只能打招呼
  • 戰勝了各個領主(如炎域領主)後得到了相應的道具,從而得到不一樣的戰鬥模式

TS版本

@blademasterDecoration
@gunslingerDecoration
@tricksterDecoration 
@royalGuardDecoration
class Dante {
  sayHi() {
    console.log(`My name is: Dante`)
  }
}

// 劍聖模式
function blademasterDecoration(target: any){
  target.prototype.blademaster = function(){console.log('I am blademaster!')}
}

// 槍神模式
function gunslingerDecoration(target){
  target.prototype.gunslinger = function(){console.log('I am gunslinger!')}
}

// 騙術師模式
function tricksterDecoration(target){
  target.prototype.trickster = function(){console.log('I am trickster!')}
}

// 皇家守衛模式
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!
複製代碼

JS版本

var Dante = /** @class */ (function () {
    function Dante() {
    }
    Dante.prototype.sayHi = function () {
        console.log("My name is: Dante");
    };
    Dante = __decorate([
        blademasterDecoration,
        gunslingerDecoration,
        tricksterDecoration,
        royalGuardDecoration
    ], Dante);
    return Dante;
}());
// 劍聖模式
function blademasterDecoration(target) {
    target.prototype.blademaster = function () { console.log('I am blademaster!'); };
}
// 槍神模式
function gunslingerDecoration(target) {
    target.prototype.gunslinger = function () { console.log('I am gunslinger!'); };
}
// 騙術師模式
function tricksterDecoration(target) {
    target.prototype.trickster = function () { console.log('I am trickster!'); };
}
// 皇家守衛模式
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!
複製代碼

Why

  • 優勢
    • 可以更加靈活的擴展類的功能
  • 缺點
    • 多層次的裝飾嵌套,增大了代碼的理解難度
  • 應用場景:想要添加新的功能同時不修改原生的代碼

適配器模式

What

定義:將一個類的接口轉換成客戶但願的另一個接口,使得本來因爲接口不兼容而不能一塊兒工做的那些類能一塊兒工做

解釋:簡單來講就是打補丁,兼容一些舊的接口

Game:LOL中卡茲克登場時,空中能夠釋放w秒人,號稱飛天螳螂。由於過於變態與英雄平衡機制不兼容,因而給他打了個補丁,W自己改動不大隻是不容許在空中釋放了。

Snipaste_2020-06-07_18-47-50.png

How

  • 螳螂本來的W能夠空中釋放,而且有治療和減速的效果
  • 螳螂改版後不能在空中釋放W

TS版本

interface TargetW{
    request(): void;
}
// 源接口
class OriginW{
    normalRequest(): void{
        console.log('個人w可以治療、減速');
    }
    flyRequest(): void{
        console.log('個人w能在空中釋放');
    }
}

class AdapterW extends OriginW implements TargetW{
    constructor(){
        super();
    }
    request(): void{
        console.log('取消了w在空中釋放的機制');
        this.normalRequest();
    }
}

let target: TargetW = new AdapterW();
target.request();
// 取消了w在空中釋放的機制
// 個人w可以治療、減速
複製代碼

JS版本

var OriginW = /** @class */ (function () {
    function OriginW() {
    }
    OriginW.prototype.normalRequest = function () {
        console.log('個人w可以治療、減速');
    };
    OriginW.prototype.flyRequest = function () {
        console.log('個人w能在空中釋放');
    };
    return OriginW;
}());
var AdapterW = /** @class */ (function (_super) {
    __extends(AdapterW, _super);
    function AdapterW() {
        return _super.call(this) || this;
    }
    AdapterW.prototype.request = function () {
        console.log('取消了w在空中釋放的機制');
        this.normalRequest();
    };
    return AdapterW;
}(OriginW));
var target = new AdapterW();
target.request();
// 取消了w在空中釋放的機制
// 個人w可以治療、減速
複製代碼

Why

  • 優勢
    • 將目標類和源接口解耦,有不錯的靈活性和可擴展性
  • 缺點
    • 做爲補丁,出現過多增大系統的複雜度。
  • 應用場景:兼容舊的接口時

組合模式

What

定義:有時又叫做部分-總體模式,它是一種將對象組合成樹狀的層次結構的模式,用來表示「部分-總體」的關係,使用戶對單個對象和組合對象具備一致的訪問性。

解釋:將對象之間的關係以🌲的形式進行表現

Game:最終幻想13中技能樹的結構,每一個加點方向爲樹幹,每一個技能點爲樹葉

Snipaste_2020-06-07_17-44-56.png

How

  • 建立一個或多個技能加點方向,好比治療,傷害等
  • 每一個加點方向添加許許多多的技能點

TS版本

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(`你選擇${this.name}方向的技能樹`)
        this.children.forEach((x: SkillTree) => x.operation())
    }
}

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

    add(node: SkillTree): void{};

    operation(): void{
        console.log(`你已學習技能點 ${this.name}`)
    }
}

let tree1: SkillTree = new SkillDirection('治療');
let tree2: SkillTree = new SkillDirection('傷害');
let leaf1: SkillTree = new SkillPoint('全體加血');
let leaf2: SkillTree = new SkillPoint('單體攻擊');
tree2.add(leaf2);
tree1.add(tree2);
tree1.add(leaf1);
tree1.operation();
// 你選擇治療方向的技能樹
// 你選擇傷害方向的技能樹
// 你已學習技能點 單體攻擊
// 你已學習技能點 全體加血
複製代碼

JS版本

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(); });
    };
    return SkillDirection;
}());
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);
    };
    return SkillPoint;
}());
var tree1 = new SkillDirection('治療');
var tree2 = new SkillDirection('傷害');
var leaf1 = new SkillPoint('全體加血');
var leaf2 = new SkillPoint('單體攻擊');
tree2.add(leaf2);
tree1.add(tree2);
tree1.add(leaf1);
tree1.operation();
// 你選擇治療方向的技能樹
// 你選擇傷害方向的技能樹
// 你已學習技能點 單體攻擊
// 你已學習技能點 全體加血
複製代碼

Why

  • 優勢
    • 局部調用和總體調用區別不大,換言之調用者沒必要擔憂調用對象是一個簡單的仍是一個複雜的結構
  • 缺點
    • 使得設計更加複雜。客戶端須要花更多時間理清類之間的層次關係樹幹直接使用了實現類,限制了接口
  • 應用場景:所描述的對象符合樹的結構,如文件管理系統

狀態模式

What

定義:對有狀態的對象,把複雜的「判斷邏輯」提取到不一樣的狀態對象中,容許狀態對象在其內部狀態發生改變時改變其行爲

解釋:不使用if-else | switch-case進行判斷,而是經過傳入狀態對象進行狀態切換

Game:只狼中葦名一心的三個狀態,完美虐殺玩家

Snipaste_2020-06-06_19-14-46.png

How

  • 每打過一個階段,葦名就切換狀態

TS版本

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

class StateOne implements State{
    change(context: WeiMingYiXin): void{
        console.log('葦名弦一郎階段');
    }
}

class StateTwo implements State{
    change(context: WeiMingYiXin): void{
        console.log('劍聖葦名一心階段');
    }
}

class StateThree implements State{
    change(context: WeiMingYiXin): void{
        console.log('雷電法王葦名一心階段');
    }
} 

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();

// 葦名弦一郎階段
// 劍聖葦名一心階段
// 雷電法王葦名一心階段
複製代碼

JS版本

var StateOne = /** @class */ (function () {
    function StateOne() {
    }
    StateOne.prototype.change = function (context) {
        console.log('葦名弦一郎階段');
    };
    return StateOne;
}());
var StateTwo = /** @class */ (function () {
    function StateTwo() {
    }
    StateTwo.prototype.change = function (context) {
        console.log('劍聖葦名一心階段');
    };
    return StateTwo;
}());
var StateThree = /** @class */ (function () {
    function StateThree() {
    }
    StateThree.prototype.change = function (context) {
        console.log('雷電法王葦名一心階段');
    };
    return StateThree;
}());
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);
    };
    return WeiMingYiXin;
}());
var ctx = new WeiMingYiXin(new StateOne());
ctx.request();
ctx.setState(new StateTwo());
ctx.request();
ctx.setState(new StateThree());
ctx.request();
// 葦名弦一郎階段
// 劍聖葦名一心階段
// 雷電法王葦名一心階段
複製代碼

Why

  • 優勢
    • 能夠很方便新增和切換狀態
  • 缺點
    • 增長了不少的類,結構和實現較爲複雜
  • 應用場景:對象的行爲依賴於它的某些屬性值,狀態的改變將致使行爲的變化

抽象工廠模式

What

定義:是一種爲訪問類提供一個建立一組相關或相互依賴對象的接口,且訪問類無須指定所要產品的具體類就能獲得同族的不一樣等級的產品的模式結構

解釋:類比於現實中的工廠,抽象工廠能夠生產多個品類的產品

Game:紅色警惕中,盟軍和蘇軍的空軍工廠和陸軍工廠分別能產出不一樣的軍備和士兵

Snipaste_2020-06-06_19-14-46.png

How

  • 盟軍陸軍工廠產出幻影坦克,空軍工廠產出戰鬥機
  • 蘇軍陸軍工廠產出犀牛坦克,空軍工廠產出飛艇

TS版本

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

class MAirForce implements AbstractAirForce{
    create(): void{
        console.log('戰鬥機已創立')
    }
}

class MGroundForce implements AbstractGroundForce{
    create(): void{
        console.log('幻影坦克已創立')
    }
}

class SAirForce implements AbstractAirForce{
    create(): void{
        console.log('飛艇已創立')
    }
}

class SGroundForce implements AbstractGroundForce{
    create(): void{
        console.log('犀牛坦克已創立')
    }
}

class MGroup implements AbstractGroup{
    createAirForce(): AbstractAirForce{
        return new MAirForce();
    }
    createGroundForce(): AbstractGroundForce{
        return new MGroundForce();
    }
}

class SGroup implements AbstractGroup{
    createAirForce(): AbstractAirForce{
        return new SAirForce();
    }
    createGroundForce(): AbstractGroundForce{
        return new SGroundForce();
    }
}

let mGroup: AbstractGroup = new MGroup();
let sGroup: AbstractGroup = new SGroup();
mGroup.createAirForce().create();
mGroup.createGroundForce().create();
sGroup.createGroundForce().create()
sGroup.createGroundForce().create();
// 戰鬥機已創立
// 幻影坦克已創立
// 犀牛坦克已創立
// 犀牛坦克已創立
複製代碼

JS版本

var MAirForce = /** @class */ (function () {
    function MAirForce() {
    }
    MAirForce.prototype.create = function () {
        console.log('戰鬥機已創立');
    };
    return MAirForce;
}());
var MGroundForce = /** @class */ (function () {
    function MGroundForce() {
    }
    MGroundForce.prototype.create = function () {
        console.log('幻影坦克已創立');
    };
    return MGroundForce;
}());
var SAirForce = /** @class */ (function () {
    function SAirForce() {
    }
    SAirForce.prototype.create = function () {
        console.log('飛艇已創立');
    };
    return SAirForce;
}());
var SGroundForce = /** @class */ (function () {
    function SGroundForce() {
    }
    SGroundForce.prototype.create = function () {
        console.log('犀牛坦克已創立');
    };
    return SGroundForce;
}());
var MGroup = /** @class */ (function () {
    function MGroup() {
    }
    MGroup.prototype.createAirForce = function () {
        return new MAirForce();
    };
    MGroup.prototype.createGroundForce = function () {
        return new MGroundForce();
    };
    return MGroup;
}());
var SGroup = /** @class */ (function () {
    function SGroup() {
    }
    SGroup.prototype.createAirForce = function () {
        return new SAirForce();
    };
    SGroup.prototype.createGroundForce = function () {
        return new SGroundForce();
    };
    return SGroup;
}());
var mGroup = new MGroup();
var sGroup = new SGroup();
mGroup.createAirForce().create();
mGroup.createGroundForce().create();
sGroup.createGroundForce().create();
sGroup.createGroundForce().create();
// 戰鬥機已創立
// 幻影坦克已創立
// 犀牛坦克已創立
// 犀牛坦克已創立
複製代碼

Why

  • 優勢
    • 增長新的具體工廠和產品族很方便,無須修改已有系統,符合「開閉原則」
  • 缺點
    • 增長新的產品等級結構很複雜,須要修改抽象工廠和全部的具體工廠類
  • 應用場景:當須要建立的對象是一系列相互關聯或相互依賴的產品族時,即可以使用抽象工廠模式

設計模式中應該遵循的準則

單一職責原則(SRP)

What

一個對象(方法)只作一件事情。

How

  • 並不是全部的耦合對象都應該分離,若是兩個職責老是同時變化,那就沒必要分離他們
  • 若是兩個發生變化的對象並無發生改變的預兆時,也沒有必要去分離他們
  • 從上述兩點可知,耦合對象應該知足有變化趨勢且二者的變化並非同步時,才應該考慮進行職責分離

Why

  • 下降了單個類或對象的複雜度
  • 利於代碼的複用和進行單元測試
  • 當一個職責需求變動時,不會影響到其餘的職責
  • 可是所以會增長代碼編寫的複雜度,同時也增大了對象之間相互聯繫的難度。如何平衡代碼的複用性和開發的難度是該原則的難點之一

最小知識原則(LKP)

What

設計程序時,應當減小對象之間的交互,避免出現a.foo1(b).foo2(c).foo3(d)的狀況出現

若是兩個對象之間沒必要直接通訊,那麼這兩個對象就不要發生直接聯繫,而是引入一個第三方對象,來承擔這些對象之間的通訊做用

How

  • 例如使用中介者模式時,兩個對象並不直接聯繫,而是經過中介者的接口進行聯繫。就像電商網站,消費者和商家經過平臺進行交易而不是直接交易

Why

  • 代碼邏輯更加清晰,避免了冗雜的鏈式調用
  • 對象之間相互獨立互不干擾,經過第三方進行聯繫。這樣利於測試以及可擴展性好

開放-封閉原則(OCP)

What

添加新功能的時候,可使用增長代碼的方式,可是不容許改動程序的源代碼

How

  • 避免使用大量if-else | switch-case等大量條件分支語句
  • 保留不變的地方,而後將變化的地方封裝起來

Why

  • 修改源代碼是一個難度基數很高的操做,常常會發生修改一個bug而引入更多的bug,而新增代碼是一種更加明智的選擇
  • 每次修改代碼後都會進行總體代碼迴歸測試,而新增模塊只須要對變化的部分進行測試
  • 提升代碼的可複用性

參考文獻

《JS設計模式與開發實踐》

DesignPatterns_TypeScript

相關文章
相關標籤/搜索