在不改变其原有的结构和功能为对象添加新功能,装饰比继承更加灵活;
适用场景:
- 功能扩展:为一个类扩展功能,为其添加额外的职责;(强调扩展)
- 动态添加撤销功能:为一个对象动态添加额外功能,同时这些被添加的功能还能被动态撤销;(强调动态)
装饰者模式优点:
- 扩展灵活:使用装饰者模式,比继承更加灵活; 使用装饰者模式扩展类功能,不会改变原来的类;
- 排列组合:对装饰类进行各种排列组合,可实现不同的扩展功能;
- 开闭原则:装饰者模式符合开闭原则,被装饰的类,和装饰类相互独立,互不干扰;
装饰者模式缺点:
- 程序复杂:需要编写更多的代码,生成更多的类,程序的复杂性增加了;
- 动态/多层装饰: 动态/多层 装饰一个类时,程序更复杂;
类图
实现代码
class Duck {
constructor(name) {
this.name = name;
}
eat(food) {
console.log(`吃${food}`);
}
}
class TangDuck {
constructor(name) {
this.duck = new Duck(name);
}
eat(food) {
this.duck.eat(food);
console.log('谢谢');
}
}
let t = new TangDuck();
t.eat('苹果');
☕️ 案例
装饰器模式是将一个对象嵌入另一个对象之中,实际上相当于这个对象被另一个对象包装起来,形成一条包装链;
请求随着这条链条依次传递到所有的对象,每个对象有处理这个请求的机会;
class Coffee {
make(water) {
return `${water}+咖啡`;
}
cost() {
return 10;
}
}
// 加奶
class MilkCoffee {
constructor(parent) {
this.parent = parent;
}
make(water) {
return `${this.parent.make(water)}+牛奶`;
}
cost() {
return this.parent.cost() + 1;
}
}
// 加糖
class SugerCoffee {
constructor(parent) {
this.parent = parent;
}
make(water) {
return `${this.parent.make(water)}+糖`;
}
cost() {
return this.parent.cost() + 2;
}
}
let coffee = new Coffee();
let milkCoffee = new MilkCoffee(coffee);
let milksugerCoffee = new SugerCoffee(milkCoffee);
console.log(milksugerCoffee.make('水') + '=' + milksugerCoffee.cost());
// 水+咖啡+牛奶+糖=13
AOP 案例
在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程;
可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术;
Function.prototype.before = function (beforeFn) {
let _this = this;
return function () {
beforeFn.apply(this, arguments);
_this.apply(this, arguments);
}
}
Function.prototype.after = function (afterFn) {
let _this = this;
return function () {
_this.apply(this, arguments);
afterFn.apply(this, arguments);
}
}
function buy(money, goods) {
console.log(`花 ${money} 买${goods}`);
}
// 返回了一个新的函数
buy = buy.before(function () {
console.log(`向媳妇申请 1 块钱.`);
});
// 返回了令一个新的函数
buy = buy.after(function () {
console.log(`把剩下的 2 毛钱还给媳妇.`);
});
buy(.8, '盐');
// 向媳妇申请 1 块钱.
// 花 0.8 买盐
// 把剩下的 2 毛钱还给媳妇.
经典场景
埋点
埋点分析,是网站分析的一种常用的数据采集方法
埋点方式
- 服务器层面的:主要是通过客户端的请求进行分析
- 客户端层面的:通过埋点进行相应的分析
- 代码埋点
- 自动化埋点:通过AOP思想对相应的方法进行统计
- 第三方实现 百度、友盟等…
-
客户端代码:
<body> <button data-name="西瓜" id="watermelon">西瓜</button> <button data-name="苹果" id="apple">苹果</button> </body> <script> Function.prototype.after = function (afterFn) { let _this = this; return function () { _this.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?name=${this.dataset.name}`; }); Array.from(document.querySelectorAll('button')).forEach(function (button) { button.addEventListener('click', click); }); </script>
-
服务端代码:
let express = require('express'); let app = express(); app.get('/', function (req, res) { console.log('name', req.query.name); res.end('ok'); }); app.listen(3000);
表单校验
<body>
<form action="">
用户名<input type="text" name="username" id="username">
密码<input type="text" name="password" id="password">
<button id="submit-btn">提交</button>
</form>
</body>
<script>
Function.prototype.before = function (beforeFn) {
let _this = this;
return function () {
let ret = beforeFn.apply(this, arguments);
if (ret)
_this.apply(this, arguments);
}
}
function submit() {
alert('提交表单');
}
submit = submit.before(function () {
let username = document.getElementById('username').value;
if (username.length < 6) {
return alert('用户名不能少于6位');
}
return true;
});
submit = submit.before(function () {
let username = document.getElementById('username').value;
if (!username) {
return alert('用户名不能为空');
}
return true;
});
document.getElementById('submit-btn').addEventListener('click', submit);
</script>
装饰器
-
webpack 支持 decorators,需要配置
"plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ]
-
类 decorators
let Hooks = { componentWillMount() { console.log('componentWillMount'); }, componentDidMount() { console.log('componentDidMount'); } } function mixins(...others) { return function (target) { // 给 Component 的原型上扩展方法 Object.assign(target.prototype, ...others); } } @mixins(Hooks) class Component { } let c = new Component(); console.log(c);
-
方法 decorators
function readonly(target, attr, descriptor) { descriptor.writable = false; } class Circle { @readonly PI = 3.14; } let c = new Circle(); c.PI = 100; console.log(c.PI)
适配器模式
上一篇