装饰器模式的定义
装饰器模式(decorator pattern):允许向一个先有的对象增添新的功能,同时又不改变其解构。
装饰器模式的优点
-
装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
-
通过使用不同的具体装饰器以及这些装饰类的排列组合,设计师可以创造出很多不同的行为组合
装饰器模式的缺点
- 由于使用装饰器模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是另一方面,由于使用装饰器模式会产生比使用继承关系更多的对象,更多的对象会使得查错变得困难,特别是这些对象看上去都很像。
装饰器模式和适配器模式的区别
-
适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的
-
装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的接口哦,但是增强原有接口的功能,或者改变原有对象的处理方法而提升性能
示例代码
恩,来一个场景:假设我们要去报名上学,通常学校都是汉语(普通话), 但是我们为了更好的教育,可能会报双语学校,那么我么就可以学会英语和汉语了
实现思路:
- 创建学校实例,
- 创建适配器,即双语学校
- 创建学校实例,调用learn方法
// 装饰器模式
// 装饰只是让原来的功能更强大,并不改变原来的功能
// 1. 学校实例
class School {
constructor (name) {
this.name = name
}
learn (language) {
console.log(`learn ${language}`)
}
}
// 2. 创建适配器(双语学校类)
class DoubleLanguageSchool {
constructor (name) {
this.school = new Durk(name)
}
learn (language) {
this.school.learn(language)
console.log(`learn English`)
}
}
// 3. 创建实例,调用learn方法
let s = new DoubleLanguageSchool('Leo')
s.learn('chinese')
常见开发场景
- 开发产品,复用类,任意组合类, 以咖啡店为例,我们在点咖啡的时候,除了最简单的咖啡,可能需要加奶,加糖,加泡沫…相对的价格什么的都会变化,我们这样子就需要使用适配模式了
// 某些情况下装饰模式回优于原型继承模式
// 1. 咖啡类
class Coffee {
make (water) {
return `${water}+咖啡`
}
cost () {
return 10
}
}
// 2. 牛奶咖啡
class MilkCoffee {
constructor (parent) {
this.parent = parent
}
make (water) {
return `${this.parent.make(water)}+牛奶`
}
cost () {
return this.parent.cost() + 5
}
}
// 加糖咖啡
class SugarCoffee {
constructor(parent) {
this.parent = parent
}
make(water) {
return `${this.parent.make(water)}+糖`
}
cost() {
return this.parent.cost() + 3
}
}
// 我们通过一下类, 就可以自由组合
let coffee = new Coffee()
let milkCoffee = new MilkCoffee(coffee)
let sugerCoffee = new SugarCoffee(milkCoffee)
let ret = milkCoffee.make('水')
let cost = milkCoffee.cost()
console.log(ret, cost)
let ret2 = sugerCoffee.make('水')
let cost2 = sugerCoffee.cost()
console.log(ret2, cost2)
-
处理事件之前和之后处理额外的逻辑
// AOP 就是在核心函数执行之前或者之后执行额外逻辑,俗称切面编程 /** * @desc 定义before方法 * @param {function} beforeFn */ Function.prototype.before = function (beforeFn) { let self = this; return function () { beforeFn.apply(this, arguments) self.apply(this, arguments) } } /** * @desc 定义after方法 * @param {function} afterFn */ Function.prototype.after = function (afterFn) { let self = this; return function () { self.apply(this, arguments) afterFn.apply(this, arguments) } } /** * @desc 购物 * @param {number} money * @param {string} product */ function buy(money, product) { console.log(`花${money}买${product}`) } buy = buy.before(() => {console.log('银行取钱')}) buy = buy.after(()=>{console.log('将剩余的钱存银行')}) // 调用 buy(1888, '游戏机')
-
使用场景之埋点
-
服务端脚本
let Koa = require('koa') let app = new Koa() const _ = require('koa-route'); let json = {} const res = { report: (ctx) => { console.log(ctx.query) let name = ctx.query.name if (json[name]) { json[name]++ } else { json[name] = 1 } ctx.body = 'hello' }, json: (ctx) => { ctx.body = json } }; app.use(_.get('/report', res.report)); app.use(_.get('/', res.json)); app.listen(3000)
-
客户管代码
<!-- 使用场景之埋点 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <button data-name="watermelon" id="watermelon">西瓜</button> <button data-name="apple" id="apple">苹果</button> <script> let watermelon = document.querySelector('#watermelon') let apple = document.querySelector('#apple') /** * @desc 定义after方法 * @param {function} afterFn */ Function.prototype.after = function (afterFn) { let self = this; return function () { self.apply(this, arguments) afterFn.apply(this, arguments) } } function click() { console.log('你点击了'+ this.dataset.name) } click = click.after(function () { let img = new Image() img.src = `http://localhost:3000/report?name=${this.dataset.name}` }) Array.from(document.querySelectorAll('button')).forEach(btn => { btn.addEventListener('click', click) }) </script> </body> </html>
-
-
使用场景之表单验证
<!-- 使用场景之埋点 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>表单效验</title> </head> <body> 用户名 <input type="text" id="username"> 密码 <input type="password" id="password"> <button id="submit">提交</button> <script> /** * @desc 定义before方法 * @param {function} beforeFn */ Function.prototype.before = function (beforeFn) { let self = this; return function () { // 这里要拿到上一次效验完毕的验证结果 if (beforeFn.apply(this, arguments)) { self.apply(this, arguments) } } } function submit(params) { console.log('提交表单') } submit = submit.before(function () { let username = document.getElementById('username').value if (!username) { alert('用户名不能为空') return false } return true }) submit = submit.before(function () { let username = document.getElementById('username').value if (username.length < 4) { alert('用户名长度最少为4') return false } return true }) document.getElementById('submit').addEventListener('click', submit) </script> </body> </html>
-
其他使用场景
- axios的请求发起之前的函数
- vue的路由调整钩子函数
- 很多场景,就不一一赘述了.
总结
多学习,有助于睡眠