One, the observer mode
1. What is the observer pattern
When there is a one-to-many dependency relationship between objects, the state of one of the objects changes, and all objects that depend on it will be notified. This is the observer pattern.
2. Actual scene
1. DOM
event
During the development process, the most common observer mode scenario is the DOM
event function. First look at the code:
document.body.addEventListener('click', () => {
alert(2)
}, false)
When the body
node is clicked, alert(2)
is triggered. From the observer mode, we subscribe to bdoy
node. When the click event is triggered, we will receive a notification.
2. Website Login
The website login function must be implemented by most students who have done platform requirements. When different modules in the website, such as Header module, Nav module, and text module, all rely on the user data obtained after login, how to implement it?
2.1 Common practice
Look at the code first:
login.succ((data => {
header.setAvatar(data.avatar) // 设置头像
nav.setAvatar(data.avatar) // 设置导航区的头像
list.refresh() // 刷新列表
})
Is this kind of code particularly familiar? Put the dependent method in the callback function. The above is the method of adding each module in the callback function of successful login. This results in a high degree of coupling between each module and the login module. When a new address bar module is added, the callback function of the login module has to be modified again, which violates the open-closed principle.
2.2 Observer Mode
Use the observer mode to optimize the above requirements.
The login module is a subscription object. The Header module, Nav module, and text module add subscriptions to the login module. When the login module changes, it will notify each module that has subscribed to the login module. code show as below:
// 登录模块js
// 登录成功后,发布“loginSucc”登录成功消息,并传递data数据
login.succ(data=> {
login.trigger('loginSucc', data)
})
// header模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
header.setAvatar(data.avatar)
})
// nav模块js
// 订阅“loginSucc”登录成功消息
login.listen('loginSucc', () => {
nav.setAvatar(data.avatar)
})
The above code uses the observer mode to reconstruct the website login function. No matter how many new business modules are added and the login function is relied on in the future, it is only necessary to add a subscription for successful login in the module without changing the login module.
3. Two-way data binding
Two-way data binding can also be achieved through the observer pattern.
Two-way refers to the view view
and the model model
. When the view changes, the model also changes. Similarly, when the model changes, the view changes synchronously.
Divided into the following steps to achieve:
3.1 Create a new publish-subscribe object
Create a new publish-subscribe object for publishing and subscribing to messages.
subscribe
: Subscription function, when other objects add subscription messages, add the callback functionpush
into thecallbacks
object array;publish
: Publish function, when a message is published, triggercallbacks
corresponding to the message incallback
.
const Pubsub = {
subscribe: function (ev, callback) {
this._callbacks || (this._callbacks = {});
(this._callbacks[ev] || (this._callbacks[ev] = [])).push(callback);
},
publish: function () {
const args = [...arguments]
const ev = args.shift()
if (!this._callbacks) return
if (!this._callbacks[ev]) return
this._callbacks[ev].forEach(callback => {
callback(...args)
})
}
}
3.2 ui
update
3.2.1 Release ui
update message
Registration document
of keyup
/ change
event, when the activation event dom
element has data-bind
time attribute, that ui
being updated release ui
update messages notify subscribers.
function eventHander (e) {
const { target } = e
const { value: propValue } = target
const propNameWhole = target.getAttribute('data-bind')
if (propNameWhole) {
// 发布ui更新消息
Pubsub.publish('ui-update-event', { propNameWhole, propValue })
}
}
console.log(document.addEventListener)
document.addEventListener('change', eventHander, false)
document.addEventListener('keyup', eventHander, false)
3.2.2 Subscribe to model
update news
All dom
elements that data-bind
attributes model
update message. When model
updated, ui
will be notified.
// 订阅model更新消息,更新后所有符合条件的dom节点都会收到通知,进行更新
Pubsub.subscribe('model-update-event', function ({propNameWhole, propValue}) {
const elements = document.querySelectorAll(`[data-bind="${propNameWhole}"]`)
elements.forEach(element => {
const elementTagName = element.tagName.toLowerCase()
const formTypeTagNames = ['input', 'select', 'textarea']
if (formTypeTagNames.includes(elementTagName)) {
element.value = propValue
} else {
element.innerHTML = propValue
}
})
})
3.3 model
update
3.3.1 Subscribe to ui
update news
Subscribe ui
update message, when ui
updating, triggered modal
update.
class Bind {
constructor () {
this.modelName = ''
}
initModel ({ modelName }) {
this.modelName = modelName
// 订阅ui更新消息
Pubsub.subscribe('ui-update-event', ({propNameWhole, propValue}) => {
const [ , _propName] = propNameWhole.split('.')
this.updateModalData(_propName, propValue)
})
}
// xxx省略xxx
updateModalData (propName, propValue) {
const propNameWhole = `${this.modelName}.${propName}`
// 发布model更新消息
Pubsub.publish('model-update-event', { propNameWhole, propValue });
}
}
3.3.2 Publish model update message
model
time updates, model
update message, this time, subscribed to model
update message ui
, will be notified.
class Bind {
constructor () {
this.modelName = ''
}
// xxx省略xxx
loadModalData (modelData) {
for (let propName in modelData) {
this.updateModalData(propName, modelData[propName])
}
}
updateModalData (propName, propValue) {
const propNameWhole = `${this.modelName}.${propName}`
// 发布model更新消息
Pubsub.publish('model-update-event', { propNameWhole, propValue });
}
}
3. Summary
As can be seen from the actual scenario examples above, the observer mode establishes a set of trigger mechanisms to help us complete loosely coupled code writing. But it should not be overused, otherwise the program will be difficult to track and understand.
Second, the decorator mode
1. What is the decorator pattern
Simply put, the decorator mode is to dynamically add functionality to the object.
There is a duck object, it will sound makeVoice
and sleep sleep
, but because it is too small to walk, the code is as follows:
const duck = {
makeVoice: () => {
console.log('我会嘎嘎嘎啦')
},
sleep: () => {
console.log('谁又不会睡觉呢')
},
init: function () {
this.makeVoice()
this.sleep()
}
}
duck.init()
When it was 3 months old, it suddenly learned to walk walk
. At this time, how to add the function of walk
duck
In most cases, we will choose to directly modify the duck duck
method, the code is as follows:
const duck = {
makeVoice: () => {
console.log('我会嘎嘎嘎啦')
},
sleep: () => {
console.log('谁又不会睡觉呢')
},
walk: () => {
console.log('哈哈哈,我会走路啦')
},
init: function () {
this.makeVoice()
this.sleep()
this.walk()
}
}
duck.init()
Happy time is always short. The longer the duck grows, the more functions it has. One day, you take time off to play and ask your friend to take care of this duck. Unfortunately, the duck is about to lay eggs. Your friend needs to add an egg laying function to the duck. This is a bit troublesome because this is the first time he takes care of this duck. Duck, he was worried about what would be affected by adding methods directly inside the duck.
So he thought of a good way, instead of directly modifying the internals of the duck, but using an external function to reference the duck and adding the egg-laying function to the external function.
code show as below:
const before = function (fn, beforeFn) {
return function () {
beforeFn.apply(this, arguments)
return fn.apply(this, arguments)
}
}
const after = function (fn, afterFn) {
return function () {
const __ = fn.apply(this, arguments)
afterFn.apply(this, arguments)
return __
}
}
const duck = {
makeVoice: function () {
console.log('我会嘎嘎嘎啦')
},
sleep: function () {
console.log('谁又不会睡觉呢')
},
walk: function () {
console.log('哈哈哈,我会走路啦')
},
init: function () {
this.makeVoice()
this.sleep()
this.walk()
}
}
after(duck.init, function egg () {
console.log('生蛋快乐~')
}).apply(duck)
This is the decorator mode, which dynamically adds functionality to the duck without directly modifying the duck itself.
2. Actual scene
1. Data reporting
The data reporting of custom events generally depends on the click event, so this click event has to undertake the original function as well as the data reporting function.
1.1 Common practice
First on the code:
const loginBtnClick = () => {
console.log('去登录')
console.log('去上报')
}
It seems that there is nothing wrong with this kind of code, which can be seen everywhere in the project, and evasion (procedure-oriented programming) is shameful but useful.
1.2 Decorator pattern practice
The above code can be reconstructed through the decorator pattern, the responsibilities are divided more finely, the code is loosely coupled, and the reusability is higher.
const after = function (fn, afterFn) {
return function () {
const __ = fn.apply(this, arguments)
afterFn.apply(this, arguments)
return __
}
}
const showLogin = function () {
console.log('去登录')
}
const log = function () {
console.log('去上报')
}
const loginBtnClick = after(showLogin, log)
loginBtnClick()
2. Dynamically increase parameters
A conventional ajax
request parameters include type
/ url
/ param
, a special case when the burst, the need ajax
parameters, add a token
parameter.
2.1 Common practice
First on the code:
const ajax = function (type, url, param) {
// 新增token参数
param.token = 'xxx'
// ...ajax请求...省略
}
Well, once again violated the open-closed principle, directly modified the ajax
function of 060def1c001e94.
2.2 Decorator approach
Through the decorator mode, before ajax
called, add the token
ajax
, the code is as follows:
const before = function (fn, beforeFn) {
return function () {
beforeFn.apply(this, arguments)
return fn.apply(this, arguments)
}
}
let ajax = function (type, url, param) {
console.log(arguments)
// ...ajax请求...省略
}
ajax = before(ajax, function (type, url, param) {
console.log(param)
param.token = 'xxx'
})
ajax('type', 'url', {name: 'tj'})
This reduces ajax
responsibility function, improved ajax
function of reusability,
3. Summary
This article describes the application scenarios of the decorator mode and the benefits it brings to us through three examples of dynamically adding functions, reporting data, and dynamically adding parameters to the duck function.
The decorator mode makes the object more stable and easy to reuse. The unstable functions can be dynamically added during personalization.
3. Chain of Responsibility Model
1. What is the chain of responsibility model
Chain of Responsibility pattern is when an object a
, there are several possible request object b
, c
, d
, e
when we b
, c
, d
, e
define a mandate to form a chain of responsibility, so a
just need to find b
Initiate a request, and then continue the request along the chain of responsibility until an object is found to handle a
.
Girls like to eat together, I am looking for someone to eat together, the code is as follows:
Hmm.... Female programmers are indeed like this. They have to write code and send requests after dinner.
const [ astrid, brooke, calliope ] = [{
name: 'astrid',
requirement: '我要吃湘菜'
},{
name: 'brooke',
requirement: '我要找10个人一起吃饭'
},{
name: 'calliope',
requirement: '我要和男朋友一起吃饭'
}]
// 是否满足Astrid的要求
function isSatisfyAstrid (user) {
// ... 省略...
}
// 是否满足Brooke的要求
function isSatisfyBrooke (user) {
// ... 省略...
}
// 是否满足Calliope的要求
function isSatisfyCalliope (user) {
// ... 省略...
}
function eatDinner () {
if (isSatisfyAstrid()) {
console.log(`我可以和 astrid 一起吃晚饭啦`)
} else if (isSatisfyBrooke()) {
console.log(`我可以和 brooke 一起吃晚饭啦`)
} else if (isSatisfyCalliope()) {
console.log(`我可以和 calliope 一起吃晚饭啦`)
} else {
console.log(`哎呀,我要一个人吃晚饭啦`)
}
}
Since astrid
, brooke
, calliope
different requirements for dinner, I need to initiate dinner requests one by one until I find someone who agrees to have dinner with me.
Here, I assume that astrid
is to eat Hunan brooke
, the requirement of 060def1c0023de is to find 10 people to join a table to eat, and calliope
is to only want to eat with her boyfriend.
The above code using if-else
is very rigid. If I have another friend davi
, I must modify the eatDinner
method again, which violates the open-closed principle and is not easy to maintain.
The following uses the chain of responsibility to optimize the above code, the code is as follows:
// 给每个人定义一个职责
const chainOrderA = new Chain(isSatisfyAstrid)
const chainOrderB = new Chain(isSatisfyBrooke)
const chainOrderC = new Chain(isSatisfyCalliope)
// 设置一下职责链的顺序
chainOrderA.setNextSuccessor(chainOrderB)
chainOrderB.setNextSuccessor(chainOrderC)
// 发起请求,这时我只需要向职责链上的第一个人请求
function eatDinner () {
chainOrder.passRequest() // 发起请求
}
Transfer the responsibility to the Chain
function, and define the next responsibility function of the responsibility setNextSuccessor
chainOrderA
-> chainOrderB
-> chainOrderC
responsibility chain. At this time, I only need to astrid
. If the request fails, it will follow Continue to request along the chain of responsibility until I find someone to have dinner with me.
The following will describe how to use the chain of responsibility mode in actual scenarios and how to implement the Chain
method. Please continue to look down.
2. Actual scene
1. 618 pre-sale merchandise orders
E-commerce websites are not allowed to launch product pre-sale activities. Assuming that before 618, if you pay a 500 deposit in advance, you will get a 100 yuan coupon, and you will get a 200 yuan deposit and you will get a 50 coupon. If you don’t pay the deposit, you won’t have a coupon. The purchase events on the day of 618 are as follows:
1.1 Common practice
Code first.
The code in this article is just an example and has nothing to do with business.
const order = function (orderType) {
if (orderType === 500) {
console.log('已预付500定金,享有100优惠券')
} else if (orderType === 200) {
console.log('已预付200定金,享有50元优惠券')
} else {
console.log('未付定金,无优惠')
}
}
order(500) // '已预付500定金,享有100优惠券'
Familiar code, a long section of if-else
judgment, is not conducive to maintenance.
1.2 Chain of Responsibility Model
Define a responsibility class Chain
.
- Receive a responsibility function
fn
as a parameter; setNextSuccessor
specifies the next responsibility function of the responsibility;passRequest
initiates a request for the responsibility functionfn
- If the returned result is
nextSuccesstor
, the request failed, and continue to request the next responsibility function in the responsibility chain; - If it does not return
nextSuccesstor
, it means that the object receiving the request has been found, the request result is returned, and the next responsibility function in the responsibility chain is no longer executed.
- If the returned result is
code show as below:
const Chain = function(fn) {
this.fn = fn;
this.successor = null;
};
Chain.prototype.setNextSuccessor = function(successor) {
return this.successor = successor;
}
Chain.prototype.passRequest = function() {
const ret = this.fn.apply(this, arguments)
if (ret === 'nextSuccessor') {
return this.successor && this.successor.passRequest.apply(this.successor, arguments);
}
return ret;
};
Then define the responsibility class instance, and setNextSuccessor
, the code is as follows:
const order500 = function (orderType) {
if (orderType === 500) {
console.log('已预付500定金,享有100优惠券')
} else {
return 'nextSuccessor'
}
}
const order200 = function (orderType) {
if (orderType === 200) {
console.log('已预付200定金,享有50元优惠券')
} else {
return 'nextSuccessor'
}
}
const chainOrder500 = new Chain(order500)
const chainOrder200 = new Chain(order200)
chainOrder500.setNextSuccessor(chainOrder200)
chainOrder500.passRequest(200)
The above code chainOrder500
and chainOrder200
into a responsibility chain. Regardless of the type of user, it only needs to initiate a request chainOrder500
chainOrder500
cannot process the request, it will continue to initiate the request along the responsibility chain until it finds a responsibility method that can handle the request. .
Through the chain of responsibility model, the complex relationship between the request sender and multiple receivers is decoupled. It is no longer necessary to know which receiver will receive the sent request, and only need to initiate a request to the first stage of the responsibility chain.
3. Summary
The chain of responsibility pattern helps us manage the code and reduces the coupling between the requesting object and the receiving request object.
The number and order of nodes in the responsibility chain model can be changed freely, and which nodes can be included in the chain can be determined at runtime.
Practical exercises can be carried out through github source code
Hope it can be helpful to you, thanks for reading ❤️
wonderful · 160def1c002f0a
[Simple and easy to understand design pattern (on)]
Welcome to follow the blog of Bump Lab: aotu.io
Or follow the AOTULabs official account (AOTULabs) and push articles from time to time.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。