Typescript 享元模式(Flyweight)

標籤: 前端 設計模式 享元模式 typescript flyweight


請仔細閱讀下面代碼,理解其中的設計理念。

享元模式

享元模式: 運用共享物件來儘可能減少不變量的內存消耗。

實際場景

如果一個應用程序用到了大量的對象,且佔用內存比較高,我們就可以考慮用享元模式來減少內存消耗。
例:統計一篇文章中所有文字的字體類型,內容,字號大小,顏色等(每一個文字都用對象存儲)。
我們可以看出來字體類型,大小,顏色等都會是大量的重複資源,可以利用享元模式減少內存的消耗。

享元模式的結構

內部變量: 不會隨環境的改變而有所不同,是可以共享的。
外部變量: 它隨環境的改變而改變的。

享元模式的例子

儲存一篇文章中所有文字的信息(十萬字級別)。
語言固定是中文,字體類型有兩種(宋體和幼圓)。
字體類型如下:

/* character-interface */
import IFontFamily from './font-family-interface';
import ILanguage from './language-interface';

export default interface ICharacterInterface {
    index: number;
    language: ILanguage;
    fontFamily: IFontFamily;
}

/* language-interface */
export default interface ILanguage {
    type: string;
    name: string;
}

/* font-family-interface */
import { FontFamilyEnum } from './font-family-enum';

export default interface IFontFamily {
    fontFamily: FontFamilyEnum;
}

/* font-family-enum */
export enum FontFamilyEnum {
    Song = '宋體',
    You = '幼圓',
}

默認的構建方法

/* language-class.ts */
import ILanguage from './models/language-interface';

export default class LanguageClass implements ILanguage{
    public type: string;
    public name: string;

    constructor(type: string, name: string) {
        this.type = type;
        this.name = name;
    }
}

/* font-family-class.ts */
import { FontFamilyEnum } from './models/font-family-enum';
import IFontFamily from './models/font-family-interface';

export default class FontFamilyClass implements IFontFamily{
    public fontFamily: FontFamilyEnum;

    constructor(type: FontFamilyEnum) {
        this.fontFamily = type;
    }
}
  • 普通方法

我們用普通的方法存儲所有的文字

/* normal-character-class.ts */
import ICharacterInterface from '../models/character-interface';
import FontFamilyClass from '../font-family-class';
import { FontFamilyEnum } from '../models/font-family-enum';
import IFontFamily from '../models/font-family-interface';
import LanguageClass from '../language-class';
import ILanguage from '../models/language-interface';

export default class NormalCharacterClass implements ICharacterInterface {
    // 文字固定是中文
    public language: ILanguage = new LanguageClass('Chinese', '中文');
    public index: number;
    public fontFamily: IFontFamily;

    constructor(index: number, type: FontFamilyEnum) {
        this.index = index;
        this.fontFamily = new FontFamilyClass(type);
    }
}

測試創建方法如下

import ICharacterInterface from './models/character-interface';
import { FontFamilyEnum } from './models/font-family-enum';
import NormalCharacterClass from './normal-way/normal-character-class';

export default class SaveCharacters {
    public static normalWay() {
        const charactersList: ICharacterInterface[] = [];
        for (let i = 0; i < 200000; i++) {
            // 模擬隨機生成類型
            const type = Math.random() > 0.5 ? FontFamilyEnum.Song : FontFamilyEnum.You;
            charactersList.push(new NormalCharacterClass(i, type));
        }
        return charactersList;
    }
}
const normalWayCharacters = SaveCharacters.normalWay();
console.log(normalWayCharacters)

在瀏覽器上運行結果如下
可以看出來普通模式創建時
20萬個字符對象佔用了23M左右的內存。

  • 享元模式

運用享元模式,我們發現language對象是恆定不變的,fontFamily對象也只會有兩種情況。
所以我們將共享內容存儲起來。

import FontFamilyClass from '../font-family-class';
import LanguageClass from '../language-class';
import { FontFamilyEnum } from '../models/font-family-enum';
import IFontFamily from '../models/font-family-interface';

export default class CharacterRegistry {
    private static _instance: CharacterRegistry;
    private _language: LanguageClass;
    private _fontFamilyMap: Map<FontFamilyEnum, IFontFamily> = new Map();

    public static get instance() {
        if (!this._instance) {
            this._instance = new CharacterRegistry();
        }
        return this._instance;
    }

    public get language() {
        if (!this._language) {
            // 懶漢策略,延時創建對象
            this._language = new LanguageClass('Chinese', '中文');
        }
        return this._language;
    }

    public getFontFamilyByType(type: FontFamilyEnum) {
        // 懶漢策略,延時創建對象
        if (!this._fontFamilyMap.has(type)) {
            this._fontFamilyMap.set(type, new FontFamilyClass(type));
        }
        return this._fontFamilyMap.get(type);
    }
}

所以在創建文字對象的方法改變成

import ICharacterInterface from '../models/character-interface';
import { FontFamilyEnum } from '../models/font-family-enum';
import IFontFamily from '../models/font-family-interface';
import ILanguage from '../models/language-interface';
import CharacterRegistry from './character-registry';

export default class FlyweightCharacterClass implements ICharacterInterface {
    public language: ILanguage = CharacterRegistry.instance.language;
    public index: number;
    public fontFamily: IFontFamily;

    constructor(index: number, type: FontFamilyEnum) {
        this.index = index;
        this.fontFamily = CharacterRegistry.instance.getFontFamilyByType(type);
    }
}

測試創建方法

import FlyweightCharacterClass from './flyweight-way/flyweight-character-class';
import ICharacterInterface from './models/character-interface';
import { FontFamilyEnum } from './models/font-family-enum';

export default class SaveCharacters {
    public static flyweightWay() {
        const charactersList: ICharacterInterface[] = [];
        for (let i = 0; i < 200000; i++) {
            // 模擬隨機生成類型
            const type = Math.random() > 0.5 ? FontFamilyEnum.Song : FontFamilyEnum.You;
            charactersList.push(new FlyweightCharacterClass(i, type));
        }
        return charactersList;
    }
}
const flyweightWayCharacters = SaveCharacters.flyweightWay();
console.log(flyweightWayCharacters)

在瀏覽器上運行結果如下
可以看出來享元模式創建時
20萬個字符對象佔用了9M左右的內存。

其中fontFamily對象和Langage對象佔用的內存明顯減少,在字體對象繼續增多的時候,fontFamily對象和Langage對象在享元模式中佔用的內存不會變化,但在普通模式中,他們會成倍的增長。享元模式的優勢也會更加顯示出來。

享元模式的利弊

利:

  • 減少了對象的創建,有效的減少大量類似對象的內存佔用。
  • 對一些公有的內部對象修改起來更加方便。

弊:

  • 將一個對象裏面的內容分開存儲,增加了系統的複雜度,增加調試和維護的成本。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章