標籤: 前端 設計模式 享元模式 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對象在享元模式中佔用的內存不會變化,但在普通模式中,他們會成倍的增長。享元模式的優勢也會更加顯示出來。
享元模式的利弊
利:
- 減少了對象的創建,有效的減少大量類似對象的內存佔用。
- 對一些公有的內部對象修改起來更加方便。
弊:
- 將一個對象裏面的內容分開存儲,增加了系統的複雜度,增加調試和維護的成本。