装饰者模式 (Decorator Pattern)又称装饰器模式,在不改变原对象的基础上,通过对其添加属性或方法来进行包装拓展,使得原有对象可以动态具有更多功能。
本质是功能动态组合,即动态地给一个对象添加额外的职责,就增加功能角度来看,使用装饰者模式比用继承更为灵活。好处是有效地把对象的核心职责和装饰功能区分开,并且通过动态增删装饰去除目标对象中重复的装饰逻辑。
一、装饰者模式生活实例
房屋装修,当毛坯房建好的时候,已经可以居住了,虽然不太舒适。一般我们自己住当然不会住毛坯,因此我们还会通水电、墙壁刷漆、铺地板、家具安装、电器安装等等步骤,让房屋渐渐具有各种各样的特性,比如墙壁刷漆和铺地板之后房屋变得更加美观,有了家具居住变得更加舒适,但这些额外的装修并没有影响房屋是用来居住的这个基本功能,这就是装饰的作用。
喝的奶茶,除了奶茶之外,还可以添加珍珠、波霸、椰果、仙草、香芋等等辅料,辅料的添加对奶茶的饮用并无影响,奶茶喝起来还是奶茶的味道,只不过辅料的添加让这杯奶茶的口感变得更多样化。
比如去咖啡厅喝咖啡,点了杯摩卡之后我们还可以选择添加糖、冰块、牛奶等等调味品,给咖啡添加特别的口感和风味,但这些调味品的添加并没有影响咖啡的基本性质,不会因为添加了调味品,咖啡就变成奶茶。
在类似场景中,这些例子有以下特点:
- 装饰不影响原有的功能,原有功能可以照常使用;
- 装饰可以增加多个,共同给目标对象添加额外功能;
二、代码实现
function OriginHouse() { } OriginHouse.prototype.getDesc = function () { console.log("空房子"); } function Furniture(house) { this.house = house; } Furniture.prototype.getDesc = function () { this.house.getDesc(); console.log("搬入家具"); } function Painting(house) { this.house = house; } Painting.prototype.getDesc = function () { this.house.getDesc(); console.log("刷房子") } let house = new OriginHouse() house = new Furniture(house) house = new Painting(house) // house.getDesc() var originHouse = { getDesc() { console.log("origin house"); } } function furniture() { console.log("furniture"); } function painting() { console.log("painting"); } originHouse.getDesc = function () { var getDesc = originHouse.getDesc; return function () { getDesc(); furniture(); painting(); } }() originHouse.getDesc();
在表现形式上,装饰者模式和适配器模式比较类似,都属于包装模式。在装饰者模式中,一个对象被另一个对象包装起来,形成一条包装链,并增加了原先对象的功能。
三、实战中的装饰者模式
-
给浏览器事件添加新功能
之前介绍的添加装饰器函数的方式,经常被用来给原有浏览器或 DOM 绑定事件上绑定新的功能,比如在 onload
上增加新的事件,或在原来的事件绑定函数上增加新的功能,或者在原本的操作上增加用户行为埋点:
window.onload = function() { console.log('原先的 onload 事件 ') } /* 发送埋点信息 */ function sendUserOperation() { console.log('埋点:用户当前行为路径为 ...') } /* 将新的功能添加到 onload 事件上 */ window.onload = function() { var originOnload = window.onload return function() { originOnload && originOnload() sendUserOperation() } }() // 输出: 原先的 onload 事件 // 输出: 埋点:用户当前行为路径为 ...
可以看到通过添加装饰函数,为 onload
事件回调增加新的方法,且并不影响原本的功能,我们可以把上面的方法提取出来作为一个工具方法:
function originDecorationFn(originObj, originKey, fn) { originObj[originKey] = function() { var originFn = originObj[originKey]; return function() { originFn && originFn(); fn(); } }() } window.onload = function() { console.log('原先的 onload 事件 ') } function sendUserOperation() { console.log('埋点:用户当前行为路径为 ...'); } originDecorationFn(window, 'onload', sendUser, sendUserOperation);