終於寫完了,寫了一個多月,每一種模式都是查閱各類資料總結出,本文較長,但願對你有所幫助,若是對你有用,請點贊支持一把,也是給予我寫做的動力javascript
設計模式表明了最佳的實踐,一般被有經驗的面向對象的軟件開發人員所採用。設計模式是軟件開發人員在軟件開發過程當中面臨的通常問題的解決方案。這些解決方案是衆多軟件開發人員通過至關長的一段時間的試驗和錯誤總結出來的。html
設計模式是一套被反覆使用的、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石同樣。vue
//checkType('165226226326','mobile')
//result:false
let checkType=function(str, type) {
switch (type) {
case 'email':
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
case 'mobile':
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
case 'tel':
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
default:
return true;
}
}
複製代碼
有如下兩個問題:java
建議的方式是給這個 API 增長一個擴展的接口:es6
let checkType=(function(){
let rules={
email(str){
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
},
mobile(str){
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
}
};
//暴露接口
return {
//校驗
check(str, type){
return rules[type]?rules[type](str):false;
},
//添加規則
addRule(type,fn){
rules[type]=fn;
}
}
})();
//調用方式
//使用mobile校驗規則
console.log(checkType.check('188170239','mobile'));
//添加金額校驗規則
checkType.addRule('money',function (str) {
return /^[0-9]+(.[0-9]{2})?$/.test(str)
});
//使用金額校驗規則
console.log(checkType.check('18.36','money'));
複製代碼
此例更詳細內容請查看-> 守候i-重構-改善代碼的各方面問題ajax
工廠模式定義一個用於建立對象的接口,這個接口由子類決定實例化哪個類。該模式使一個類的實例化延遲到了子類。而子類能夠重寫接口方法以便建立的時候指定本身的對象類型。算法
class Product {
constructor(name) {
this.name = name
}
init() {
console.log('init')
}
fun() {
console.log('fun')
}
}
class Factory {
create(name) {
return new Product(name)
}
}
// use
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()
複製代碼
當被應用到錯誤的問題類型上時,這一模式會給應用程序引入大量沒必要要的複雜性.除非爲建立對象提供一個接口是咱們編寫的庫或者框架的一個設計上目標,不然我會建議使用明確的構造器,以免沒必要要的開銷。vuex
因爲對象的建立過程被高效的抽象在一個接口後面的事實,這也會給依賴於這個過程可能會有多複雜的單元測試帶來問題。express
class jQuery {
constructor(selector) {
super(selector)
}
add() {
}
// 此處省略若干API
}
window.$ = function(selector) {
return new jQuery(selector)
}
複製代碼
在大型應用中,咱們可能須要將應用分割成小一些的代碼塊,而且只在須要的時候才從服務器加載一個模塊。爲了簡化,Vue 容許你以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件須要被渲染的時候纔會觸發該工廠函數,且會把結果緩存起來供將來重渲染。例如:編程
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回調傳遞組件定義
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
複製代碼
一個類只有一個實例,並提供一個訪問它的全局訪問點。
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if (this.state === 'show') {
alert('已經顯示')
return
}
this.state = 'show'
console.log('登陸框顯示成功')
}
hide() {
if (this.state === 'hide') {
alert('已經隱藏')
return
}
this.state = 'hide'
console.log('登陸框隱藏成功')
}
}
LoginForm.getInstance = (function () {
let instance
return function () {
if (!instance) {
instance = new LoginForm()
}
return instance
}
})()
let obj1 = LoginForm.getInstance()
obj1.show()
let obj2 = LoginForm.getInstance()
obj2.hide()
console.log(obj1 === obj2)
複製代碼
將一個類的接口轉化爲另一個接口,以知足用戶需求,使類之間接口不兼容問題經過適配器得以解決。
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充電頭 適配器轉Type-c充電頭
複製代碼
// 本身封裝的ajax, 使用方式以下
ajax({
url: '/getData',
type: 'Post',
dataType: 'json',
data: {
test: 111
}
}).done(function() {})
// 由於歷史緣由,代碼中全都是:
// $.ajax({....})
// 作一層適配器
var $ = {
ajax: function (options) {
return ajax(options)
}
}
複製代碼
<template>
<div id="example">
<p>Original message: "{{ message }}"</p> <!-- Hello -->
<p>Computed reversed message: "{{ reversedMessage }}"</p> <!-- olleH -->
</div>
</template>
<script type='text/javascript'>
export default {
name: 'demo',
data() {
return {
message: 'Hello'
}
},
computed: {
reversedMessage: function() {
return this.message.split('').reverse().join('')
}
}
}
</script>
複製代碼
適配器與代理模式類似
class Cellphone {
create() {
console.log('生成一個手機')
}
}
class Decorator {
constructor(cellphone) {
this.cellphone = cellphone
}
create() {
this.cellphone.create()
this.createShell(cellphone)
}
createShell() {
console.log('生成手機殼')
}
}
// 測試代碼
let cellphone = new Cellphone()
cellphone.create()
console.log('------------')
let dec = new Decorator(cellphone)
dec.create()
複製代碼
是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問
假設當A 在心情好的時候收到花,小明表白成功的概率有 60%,而當A 在心情差的時候收到花,小明表白的成功率無限趨近於0。 小明跟A 剛剛認識兩天,還沒法辨別A 何時心情好。若是不合時宜地把花送給A,花 被直接扔掉的可能性很大,這束花但是小明吃了7 天泡麪換來的。 可是A 的朋友B 卻很瞭解A,因此小明只管把花交給B,B 會監聽A 的心情變化,而後選 擇A 心情好的時候把花轉交給A,代碼以下:
let Flower = function() {}
let xiaoming = {
sendFlower: function(target) {
let flower = new Flower()
target.receiveFlower(flower)
}
}
let B = {
receiveFlower: function(flower) {
A.listenGoodMood(function() {
A.receiveFlower(flower)
})
}
}
let A = {
receiveFlower: function(flower) {
console.log('收到花'+ flower)
},
listenGoodMood: function(fn) {
setTimeout(function() {
fn()
}, 1000)
}
}
xiaoming.sendFlower(B)
複製代碼
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let ul = document.querySelector('#ul');
ul.addEventListener('click', event => {
console.log(event.target);
});
</script>
複製代碼
處理請求速度可能有差異,非直接訪問存在開銷
裝飾者模式實現上和代理模式相似
爲子系統的一組接口提供一個一致的界面,定義了一個高層接口,這個接口使子系統更加容易使用
let addMyEvent = function (el, ev, fn) {
if (el.addEventListener) {
el.addEventListener(ev, fn, false)
} else if (el.attachEvent) {
el.attachEvent('on' + ev, fn)
} else {
el['on' + ev] = fn
}
};
複製代碼
let myEvent = {
// ...
stop: e => {
e.stopPropagation();
e.preventDefault();
}
};
複製代碼
參考: 大話設計模式
定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使它們可以自動更新本身,當一個對象的改變須要同時改變其它對象,而且它不知道具體有多少對象須要改變的時候,就應該考慮使用觀察者模式。
// 主題 保存狀態,狀態變化以後觸發全部觀察者對象
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)
複製代碼
document.body.addEventListener('click', function() {
console.log('hello world!');
});
document.body.click()
複製代碼
過分使用會致使對象與對象之間的聯繫弱化,會致使程序難以跟蹤維護和理解
容許一個對象在其內部狀態改變的時候改變它的行爲,對象看起來彷佛修改了它的類
// 狀態 (弱光、強光、關燈)
class State {
constructor(state) {
this.state = state
}
handle(context) {
console.log(`this is ${this.state} light`)
context.setState(this)
}
}
class Context {
constructor() {
this.state = null
}
getState() {
return this.state
}
setState(state) {
this.state = state
}
}
// test
let context = new Context()
let weak = new State('weak')
let strong = new State('strong')
let off = new State('off')
// 弱光
weak.handle(context)
console.log(context.getState())
// 強光
strong.handle(context)
console.log(context.getState())
// 關閉
strong.handle(context)
console.log(context.getState())
複製代碼
提供一種方法順序一個聚合對象中各個元素,而又不暴露該對象的內部表示。
class Iterator {
constructor(conatiner) {
this.list = conatiner.list
this.index = 0
}
next() {
if (this.hasNext()) {
return this.list[this.index++]
}
return null
}
hasNext() {
if (this.index >= this.list.length) {
return false
}
return true
}
}
class Container {
constructor(list) {
this.list = list
}
getIterator() {
return new Iterator(this)
}
}
// 測試代碼
let container = new Container([1, 2, 3, 4, 5])
let iterator = container.getIterator()
while(iterator.hasNext()) {
console.log(iterator.next())
}
複製代碼
對於集合內部結果經常變化各異,不想暴露其內部結構的話,但又想讓客戶代碼透明的訪問其中的元素,可使用迭代器模式
橋接模式(Bridge)將抽象部分與它的實現部分分離,使它們均可以獨立地變化。
class Color {
constructor(name){
this.name = name
}
}
class Shape {
constructor(name,color){
this.name = name
this.color = color
}
draw(){
console.log(`${this.color.name} ${this.name}`)
}
}
//測試
let red = new Color('red')
let yellow = new Color('yellow')
let circle = new Shape('circle', red)
circle.draw()
let triangle = new Shape('triangle', yellow)
triangle.draw()
複製代碼
class TrainOrder {
create () {
console.log('建立火車票訂單')
}
}
class HotelOrder {
create () {
console.log('建立酒店訂單')
}
}
class TotalOrder {
constructor () {
this.orderList = []
}
addOrder (order) {
this.orderList.push(order)
return this
}
create () {
this.orderList.forEach(item => {
item.create()
})
return this
}
}
// 能夠在購票網站買車票同時也訂房間
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()
複製代碼
若是經過組合模式建立了太多的對象,那麼這些對象可能會讓系統負擔不起。
原型模式(prototype)是指用原型實例指向建立對象的種類,而且經過拷貝這些原型建立新的對象。
class Person {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Student extends Person {
constructor(name) {
super(name)
}
sayHello() {
console.log(`Hello, My name is ${this.name}`)
}
}
let student = new Student("xiaoming")
student.sayHello()
複製代碼
原型模式,就是建立一個共享的原型,經過拷貝這個原型來建立新的類,用於建立重複的對象,帶來性能上的提高。
定義一系列的算法,把它們一個個封裝起來,而且使它們能夠互相替換
<html>
<head>
<title>策略模式-校驗表單</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
</head>
<body>
<form id = "registerForm" method="post" action="http://xxxx.com/api/register">
用戶名:<input type="text" name="userName">
密碼:<input type="text" name="password">
手機號碼:<input type="text" name="phoneNumber">
<button type="submit">提交</button>
</form>
<script type="text/javascript">
// 策略對象
const strategies = {
isNoEmpty: function (value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
isNoSpace: function (value, errorMsg) {
if (value.trim() === '') {
return errorMsg;
}
},
minLength: function (value, length, errorMsg) {
if (value.trim().length < length) {
return errorMsg;
}
},
maxLength: function (value, length, errorMsg) {
if (value.length > length) {
return errorMsg;
}
},
isMobile: function (value, errorMsg) {
if (!/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[7]|18[0|1|2|3|5|6|7|8|9])\d{8}$/.test(value)) {
return errorMsg;
}
}
}
// 驗證類
class Validator {
constructor() {
this.cache = []
}
add(dom, rules) {
for(let i = 0, rule; rule = rules[i++];) {
let strategyAry = rule.strategy.split(':')
let errorMsg = rule.errorMsg
this.cache.push(() => {
let strategy = strategyAry.shift()
strategyAry.unshift(dom.value)
strategyAry.push(errorMsg)
return strategies[strategy].apply(dom, strategyAry)
})
}
}
start() {
for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
let errorMsg = validatorFunc()
if (errorMsg) {
return errorMsg
}
}
}
}
// 調用代碼
let registerForm = document.getElementById('registerForm')
let validataFunc = function() {
let validator = new Validator()
validator.add(registerForm.userName, [{
strategy: 'isNoEmpty',
errorMsg: '用戶名不可爲空'
}, {
strategy: 'isNoSpace',
errorMsg: '不容許以空白字符命名'
}, {
strategy: 'minLength:2',
errorMsg: '用戶名長度不能小於2位'
}])
validator.add(registerForm.password, [ {
strategy: 'minLength:6',
errorMsg: '密碼長度不能小於6位'
}])
validator.add(registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '請輸入正確的手機號碼格式'
}])
return validator.start()
}
registerForm.onsubmit = function() {
let errorMsg = validataFunc()
if (errorMsg) {
alert(errorMsg)
return false
}
}
</script>
</body>
</html>
複製代碼
運用共享技術有效地支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式,它是一種對象結構型模式
let examCarNum = 0 // 駕考車總數
/* 駕考車對象 */
class ExamCar {
constructor(carType) {
examCarNum++
this.carId = examCarNum
this.carType = carType ? '手動檔' : '自動檔'
this.usingState = false // 是否正在使用
}
/* 在本車上考試 */
examine(candidateId) {
return new Promise((resolve => {
this.usingState = true
console.log(`考生- ${ candidateId } 開始在${ this.carType }駕考車- ${ this.carId } 上考試`)
setTimeout(() => {
this.usingState = false
console.log(`%c考生- ${ candidateId } 在${ this.carType }駕考車- ${ this.carId } 上考試完畢`, 'color:#f40')
resolve() // 0~2秒後考試完畢
}, Math.random() * 2000)
}))
}
}
/* 手動檔汽車對象池 */
ManualExamCarPool = {
_pool: [], // 駕考車對象池
_candidateQueue: [], // 考生隊列
/* 註冊考生 ID 列表 */
registCandidates(candidateList) {
candidateList.forEach(candidateId => this.registCandidate(candidateId))
},
/* 註冊手動檔考生 */
registCandidate(candidateId) {
const examCar = this.getManualExamCar() // 找一個未被佔用的手動檔駕考車
if (examCar) {
examCar.examine(candidateId) // 開始考試,考完了讓隊列中的下一個考生開始考試
.then(() => {
const nextCandidateId = this._candidateQueue.length && this._candidateQueue.shift()
nextCandidateId && this.registCandidate(nextCandidateId)
})
} else this._candidateQueue.push(candidateId)
},
/* 註冊手動檔車 */
initManualExamCar(manualExamCarNum) {
for (let i = 1; i <= manualExamCarNum; i++) {
this._pool.push(new ExamCar(true))
}
},
/* 獲取狀態爲未被佔用的手動檔車 */
getManualExamCar() {
return this._pool.find(car => !car.usingState)
}
}
ManualExamCarPool.initManualExamCar(3) // 一共有3個駕考車
ManualExamCarPool.registCandidates([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) // 10個考生來考試
複製代碼
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法和封裝子類中全部方法的執行順序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法。
class Beverage {
constructor({brewDrink, addCondiment}) {
this.brewDrink = brewDrink
this.addCondiment = addCondiment
}
/* 燒開水,共用方法 */
boilWater() { console.log('水已經煮沸=== 共用') }
/* 倒杯子裏,共用方法 */
pourCup() { console.log('倒進杯子裏===共用') }
/* 模板方法 */
init() {
this.boilWater()
this.brewDrink()
this.pourCup()
this.addCondiment()
}
}
/* 咖啡 */
const coffee = new Beverage({
/* 沖泡咖啡,覆蓋抽象方法 */
brewDrink: function() { console.log('沖泡咖啡') },
/* 加調味品,覆蓋抽象方法 */
addCondiment: function() { console.log('加點奶和糖') }
})
coffee.init()
複製代碼
使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止
// 請假審批,須要組長審批、經理審批、總監審批
class Action {
constructor(name) {
this.name = name
this.nextAction = null
}
setNextAction(action) {
this.nextAction = action
}
handle() {
console.log( `${this.name} 審批`)
if (this.nextAction != null) {
this.nextAction.handle()
}
}
}
let a1 = new Action("組長")
let a2 = new Action("經理")
let a3 = new Action("總監")
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()
複製代碼
將一個請求封裝成一個對象,從而讓你使用不一樣的請求把客戶端參數化,對請求排隊或者記錄請求日誌,能夠提供命令的撤銷和恢復功能。
// 接收者類
class Receiver {
execute() {
console.log('接收者執行請求')
}
}
// 命令者
class Command {
constructor(receiver) {
this.receiver = receiver
}
execute () {
console.log('命令');
this.receiver.execute()
}
}
// 觸發者
class Invoker {
constructor(command) {
this.command = command
}
invoke() {
console.log('開始')
this.command.execute()
}
}
// 倉庫
const warehouse = new Receiver();
// 訂單
const order = new Command(warehouse);
// 客戶
const client = new Invoker(order);
client.invoke()
複製代碼
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣之後就可將該對象恢復到保存的狀態。
//備忘類
class Memento{
constructor(content){
this.content = content
}
getContent(){
return this.content
}
}
// 備忘列表
class CareTaker {
constructor(){
this.list = []
}
add(memento){
this.list.push(memento)
}
get(index){
return this.list[index]
}
}
// 編輯器
class Editor {
constructor(){
this.content = null
}
setContent(content){
this.content = content
}
getContent(){
return this.content
}
saveContentToMemento(){
return new Memento(this.content)
}
getContentFromMemento(memento){
this.content = memento.getContent()
}
}
//測試代碼
let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent('111')
editor.setContent('222')
careTaker.add(editor.saveContentToMemento())
editor.setContent('333')
careTaker.add(editor.saveContentToMemento())
editor.setContent('444')
console.log(editor.getContent()) //444
editor.getContentFromMemento(careTaker.get(1))
console.log(editor.getContent()) //333
editor.getContentFromMemento(careTaker.get(0))
console.log(editor.getContent()) //222
複製代碼
解除對象與對象之間的緊耦合關係。增長一箇中介者對象後,全部的 相關對象都經過中介者對象來通訊,而不是互相引用,因此當一個對象發生改變時,只須要通知 中介者對象便可。中介者使各對象之間耦合鬆散,並且能夠獨立地改變它們之間的交互。中介者 模式使網狀的多對多關係變成了相對簡單的一對多關係(相似於觀察者模式,可是單向的,由中介者統一管理。)
class A {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number * 10)
}
setB() {
let number = this.a.number
this.b.setNumber(number / 10)
}
}
let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(10, m)
console.log(a.number, b.number)
b.setNumber(10, m)
console.log(a.number, b.number)
複製代碼
給定一個語言, 定義它的文法的一種表示,並定義一個解釋器, 該解釋器使用該表示來解釋語言中的句子。
此例來自心譚博客
class Context {
constructor() {
this._list = []; // 存放 終結符表達式
this._sum = 0; // 存放 非終結符表達式(運算結果)
}
get sum() {
return this._sum;
}
set sum(newValue) {
this._sum = newValue;
}
add(expression) {
this._list.push(expression);
}
get list() {
return [...this._list];
}
}
class PlusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = ++context.sum;
}
}
class MinusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = --context.sum;
}
}
/** 如下是測試代碼 **/
const context = new Context();
// 依次添加: 加法 | 加法 | 減法 表達式
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new MinusExpression());
// 依次執行: 加法 | 加法 | 減法 表達式
context.list.forEach(expression => expression.interpret(context));
console.log(context.sum);
複製代碼
表示一個做用於某對象結構中的各元素的操做。它使你能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。
// 訪問者
class Visitor {
constructor() {}
visitConcreteElement(ConcreteElement) {
ConcreteElement.operation()
}
}
// 元素類
class ConcreteElement{
constructor() {
}
operation() {
console.log("ConcreteElement.operation invoked");
}
accept(visitor) {
visitor.visitConcreteElement(this)
}
}
// client
let visitor = new Visitor()
let element = new ConcreteElement()
elementA.accept(visitor)
複製代碼