JavaScript設(shè)計模式

設(shè)計模式分類(23種設(shè)計模式)


  • 創(chuàng)建型
    • 單例模式
    • 原型模式
    • 工廠模式
    • 抽象工廠模式
    • 建造者模式

  • 結(jié)構(gòu)型
    • 適配器模式
    • 裝飾器模式
    • 代理模式
    • 外觀模式
    • 橋接模式
    • 組合模式
    • 享元模式

  • 行為型
    • 觀察者模式
    • 迭代器模式
    • 策略模式
    • 模板方法模式
    • 職責(zé)鏈模式
    • 命令模式
    • 備忘錄模式
    • 狀態(tài)模式
    • 訪問者模式
    • 中介者模式
    • 解釋器模式


工廠模式

什么是工廠模式朱浴?一個工廠接到一筆訂單(傳參)邻薯,然后根據(jù)這個訂單類型(參數(shù))來安排產(chǎn)品線(實例化哪個類)世曾,當(dāng)然客戶可以要求一些產(chǎn)品的工藝屬性(抽象工廠)。這其中廠長(工廠模式)只負責(zé)調(diào)度,即安排產(chǎn)品零件流水線。你應(yīng)該知道的是洁墙,工廠有個特點就是產(chǎn)出體量大、相似度高的產(chǎn)品戒财。如果你要做單一定制化的產(chǎn)品热监,那這筆訂單給工廠就不適用了。

舉個例子:

  • 編程中饮寞,在一個 A 類中通過 new 的方式實例化了類 B孝扛,那么 A 類和 B 類之間就存在關(guān)聯(lián)(耦合);
  • 后期因為需要修改了 B 類的代碼和使用方式列吼,比如構(gòu)造函數(shù)中傳入?yún)?shù),那么 A 類也要跟著修改苦始,一個類的依賴可能影響不大寞钥,但若有多個類依賴了 B 類,那么這個工作量將會相當(dāng)?shù)拇竽把。菀壮霈F(xiàn)修改錯誤理郑,也會產(chǎn)生很多的重復(fù)代碼,這無疑是件非常痛苦的事咨油;
  • 這種情況下您炉,就需要將創(chuàng)建實例的工作從調(diào)用方(A類)中分離,與調(diào)用方解耦役电,也就是使用工廠方法創(chuàng)建實例的工作封裝起來(減少代碼重復(fù))赚爵,由工廠管理對象的創(chuàng)建邏輯,調(diào)用方不需要知道具體的創(chuàng)建過程宴霸,只管使用囱晴,而降低調(diào)用者因為創(chuàng)建邏輯導(dǎo)致的錯誤;
使用場景
  • 處理大量具有相同屬性的小對象瓢谢;
  • 對象的構(gòu)建十分復(fù)雜畸写,需要依賴具體環(huán)境創(chuàng)建不同實例;

工廠模式的三種實現(xiàn)方法簡單工廠模式氓扛、工廠方法模式枯芬、抽象工廠模式

1. 簡單工廠模式

簡單工廠模式又叫靜態(tài)工廠模式采郎,由一個工廠對象決定創(chuàng)建某一種產(chǎn)品對象類的實例千所。主要用來創(chuàng)建同一類對象。

//User類
class User {
  //構(gòu)造器
  constructor(opt) {
    this.name = opt.name;
    this.viewPage = opt.viewPage;
  }

  //靜態(tài)方法
  static getInstance(role) {
    switch (role) {
      case 'superAdmin':
        return new User({ name: '超級管理員', viewPage: ['首頁', '通訊錄', '發(fā)現(xiàn)頁', '應(yīng)用數(shù)據(jù)', '權(quán)限管理'] });
        break;
      case 'admin':
        return new User({ name: '管理員', viewPage: ['首頁', '通訊錄', '發(fā)現(xiàn)頁', '應(yīng)用數(shù)據(jù)'] });
        break;
      case 'user':
        return new User({ name: '普通用戶', viewPage: ['首頁', '通訊錄', '發(fā)現(xiàn)頁'] });
        break;
      default:
        throw new Error('參數(shù)錯誤, 可選參數(shù):superAdmin蒜埋、admin淫痰、user')
    }
  }
}

//調(diào)用
let superAdmin = User.getInstance('superAdmin');
let admin = User.getInstance('admin');
let normalUser = User.getInstance('user');

User就是一個簡單工廠,在該函數(shù)中有3個實例中分別對應(yīng)不同的權(quán)限的用戶整份。當(dāng)我們調(diào)用工廠函數(shù)時待错,只需要傳遞superAdmin, admin, user這三個可選參數(shù)中的一個獲取對應(yīng)的實例對象。
簡單工廠的優(yōu)點在于烈评,你只需要一個正確的參數(shù)火俄,就可以獲取到你所需要的對象,而無需知道其創(chuàng)建的具體細節(jié)讲冠。但是在函數(shù)內(nèi)包含了所有對象的創(chuàng)建邏輯(構(gòu)造函數(shù))和判斷邏輯的代碼瓜客,每增加新的構(gòu)造函數(shù)還需要修改判斷邏輯代碼。當(dāng)我們的對象不是上面的3個而是30個或更多時,這個函數(shù)會成為一個龐大的超級函數(shù)谱仪,便得難以維護玻熙。所以,簡單工廠只能作用于創(chuàng)建的對象數(shù)量較少芽卿,對象的創(chuàng)建邏輯不復(fù)雜時使用揭芍。

2. 工廠方法模式

工廠方法模式的本意是將實際創(chuàng)建對象的工作推遲到子類中,這樣核心類就變成了抽象類卸例。這樣添加新的類時就無需修改工廠方法称杨,只需要將子類注冊進工廠方法的原型對象中即可。

class User {
  constructor(name = '', viewPage = []) {
    if(new.target === User) {
      throw new Error('抽象類不能實例化!');
    }
    this.name = name;
    this.viewPage = viewPage;
  }
}

class UserFactory extends User {
  constructor(name, viewPage) {
    super(name, viewPage)
  }
  create(role) {
    switch (role) {
      case 'superAdmin': 
        return new UserFactory( '超級管理員', ['首頁', '通訊錄', '發(fā)現(xiàn)頁', '應(yīng)用數(shù)據(jù)', '權(quán)限管理'] );
        break;
      case 'admin':
        return new UserFactory( '普通用戶', ['首頁', '通訊錄', '發(fā)現(xiàn)頁'] );
        break;
      case 'user':
        return new UserFactory( '普通用戶', ['首頁', '通訊錄', '發(fā)現(xiàn)頁'] );
        break;
      default:
        throw new Error('參數(shù)錯誤, 可選參數(shù):superAdmin筷转、admin姑原、user')
    }
  }
}

let userFactory = new UserFactory();
let superAdmin = userFactory.create('superAdmin');
let admin = userFactory.create('admin');
let user = userFactory.create('user');
3. 工廠方法模式

抽象工廠只留對外的口子,不做事呜舒,留給外界覆蓋(子類重寫接口方法以便創(chuàng)建的時候指定自己的對象類型)锭汛。主要用于對產(chǎn)品類簇的創(chuàng)建,不直接生成實例(簡單工廠模式和工廠方法模式都是生成實例)袭蝗。

  • 抽象類是一種聲明但不能使用的類唤殴,子類必須先實現(xiàn)其方法才能調(diào)用;
  • 可以在抽象類中定義一套規(guī)范,供子類去繼承實現(xiàn);
// 抽象工廠
function AbstractFactory(subType, superType) {
    if (typeof AbstractFactory[superType] === 'function') {
        //緩存類
        function F() { }
        //繼承父類屬性和方法
        F.prototype = new AbstractFactory[superType]();
        //將子類 constructor 指向子類(自己)
        subType.prototype.constructor = subType;
        //子類原型繼承緩存類(父類)
        subType.prototype = new F();
    } else {
        //不存在該抽象類拋出錯誤
        throw new Error('抽象類不存在')
    }
}

// 抽象類
AbstractFactory.Phone = function () {
    this.type = 'Phone';
}
AbstractFactory.Phone.prototype = {
    showType: function () {
        return new Error('Phone 抽象方法 showType 不能調(diào)用');
    },
    showPrice: function () {
        return new Error('Phone 抽象方法 showPrice 不能調(diào)用');
    },
    showColor: function () {
        return new Error('Phone 抽象方法 showColor 不能調(diào)用');
    }
}

AbstractFactory.Pad = function () {
    this.type = 'Pad';
}
AbstractFactory.Pad.prototype = {
    showType: function () {
        return new Error('Pad 抽象方法 showType 不能調(diào)用');
    },
    showPrice: function () {
        return new Error('Pad 抽象方法 showPrice 不能調(diào)用');
    },
    showColor: function () {
        return new Error('Pad 抽象方法 showColor 不能調(diào)用');
    }
}

// 抽象工廠實現(xiàn)對抽象類的繼承
function Iphone(type, price, color) {
    this.type = type;
    this.price = price;
    this.color = color;
}

//抽象工廠實現(xiàn)對 Phone 抽象類的繼承
AbstractFactory(Iphone, 'Phone');
Iphone.prototype.showType = function () {
    return this.type;
}
Iphone.prototype.showPrice = function () {
    return this.price;
}
Iphone.prototype.showColor = function () {
    return this.color;
}

function Ipad(type, price, color) {
    this.type = type;
    this.price = price;
    this.color = color;
}
AbstractFactory(Ipad, 'Pad');
Ipad.prototype.showType = function () {
    return this.type;
}
Ipad.prototype.showPrice = function () {
    return this.price;
}
Ipad.prototype.showColor = function () {
    return this.color;
}

// 實例
var iphone5s = new Iphone('iphone 5s', 3000, '白色');
console.log('今天剛買了' + iphone5s.showType() + '到腥,價格是' + iphone5s.showPrice() + '朵逝,' + iphone5s.showColor())

var iphone8s = new Iphone('iphone 8s', 8000, '白色');
console.log('今天剛買了' + iphone8s.showType() + ',價格是' + iphone8s.showPrice() + '乡范,' + iphone8s.showColor())

var ipad = new Ipad('ipad air', 2000, '騷紅色');
console.log('今天剛買了' + ipad.showType() + '配名,價格是' + ipad.showPrice() + ',' + ipad.showColor())


單例模式

一個類只有一個實例晋辆,并提供一個訪問它的全局訪問點渠脉。
詳細可參考:https://segmentfault.com/a/1190000022831974

  • 利用閉包實現(xiàn)單例模式
function Singleton(name) {
    this.name = name
}

var proxySingle = (function(){
    var instance
    return function(name) {
        if(!instance) {
            instance = new Singleton(name)
        }
        return instance
    }
})()


適配器模式

將一個類(對象)的接口(方法或?qū)傩裕┺D(zhuǎn)化成客戶希望的另外一個接口(方法或?qū)傩裕沟迷居捎诮涌诓患嫒荻荒芤黄鸸ぷ鞯哪切╊悾▽ο螅┛梢哉f(xié)作瓶佳。簡單理解就是為兼容而生的 “轉(zhuǎn)換器”芋膘。

class Plug {
  getName() {
    return 'iphone充電頭';
  }
}

class Target {
  constructor() {
    this.plug = new Plug();
  }
  getName() {
    return this.plug.getName() + ' 適配器Type-c充電頭';
  }
}

let target = new Target();
target.getName(); // iphone充電頭 適配器轉(zhuǎn)Type-c充電頭


觀察者模式

定義了一種一對多的關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象霸饲,這個主題對象的狀態(tài)發(fā)生變化時就會通知所有的觀察者對象索赏,使它們能夠自動更新自己,當(dāng)一個對象的改變需要同時改變其它對象贴彼,并且它不知道具體有多少對象需要改變的時候,就應(yīng)該考慮使用觀察者模式埃儿。

// 主題 保存狀態(tài)器仗,狀態(tài)變化之后觸發(fā)所有觀察者對象
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observer.update()
    })
  }
  attach(observer) {
    this.observers.push(observer)
  }
}

// 觀察者
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this)
  }
  update() {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}

// 測試
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('02', s)

s.setState(12)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子精钮,更是在濱河造成了極大的恐慌威鹿,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轨香,死亡現(xiàn)場離奇詭異忽你,居然都是意外死亡,警方通過查閱死者的電腦和手機臂容,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門科雳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脓杉,你說我怎么就攤上這事糟秘。” “怎么了球散?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵尿赚,是天一觀的道長。 經(jīng)常有香客問我蕉堰,道長凌净,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任屋讶,我火速辦了婚禮冰寻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丑婿。我一直安慰自己性雄,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布羹奉。 她就那樣靜靜地躺著秒旋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诀拭。 梳的紋絲不亂的頭發(fā)上迁筛,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音耕挨,去河邊找鬼细卧。 笑死,一個胖子當(dāng)著我的面吹牛筒占,可吹牛的內(nèi)容都是我干的贪庙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼翰苫,長吁一口氣:“原來是場噩夢啊……” “哼止邮!你這毒婦竟也來了这橙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤导披,失蹤者是張志新(化名)和其女友劉穎屈扎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撩匕,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鹰晨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了止毕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片模蜡。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖滓技,靈堂內(nèi)的尸體忽然破棺而出哩牍,到底是詐尸還是另有隱情,我是刑警寧澤令漂,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布膝昆,位于F島的核電站,受9級特大地震影響叠必,放射性物質(zhì)發(fā)生泄漏荚孵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一纬朝、第九天 我趴在偏房一處隱蔽的房頂上張望收叶。 院中可真熱鬧,春花似錦共苛、人聲如沸判没。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澄峰。三九已至,卻和暖如春辟犀,著一層夾襖步出監(jiān)牢的瞬間俏竞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工堂竟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魂毁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓出嘹,卻偏偏與公主長得像席楚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子税稼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容