装饰器模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
适用场景
我们可以在以下几种场景中考虑使用装饰器模式
- 在不影响其它对象的情况下,以动态、透明的方式给单个对象添加新的行为或特征
- 处理可以撤销的行为或特征(即行为或特征是临时属性)
- 当使用子类扩展的方式不切实际的时候,可考虑使用装饰器模式
例如在LOL中每个英雄都有血条、等级,但是不同英雄的技能跟外观不同,不可能每个英雄都去实现一个子类,这时候就可以采用装饰器模式。
我们可以自由的向对象中注入行为,而且这些行为是可控的。
实现
Java
适配器模式由以下四种角色组成:
- 抽象组件角色(Component): 定义可以动态添加任务的对象的接口
- 具体组件角色(ConcreteComponent):定义一个要被装饰器装饰的对象,即 Component 的具体实现
- 抽象装饰器(Decorator): 维护对象和其子类的引用
- 具体装饰器角色(ConcreteDecorator):向对象添加新的功能或行为特征
例如我们实现一个飞机类,随着等级的增加他可以使用不同的武器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
abstract class APlane { public abstract void fire(); }
class Plane extends APlane { @Override public void fire() { System.out.println("firing ordinary bullets"); } }
abstract class ADecorator extends APlane { protected APlane plane;
public ADecorator(APlane plane) { this.plane = plane; }
@Override public void fire() { plane.fire(); } }
class MissileDecorator extends ADecorator { public MissileDecorator(APlane plane) { super(plane); }
public void up() { System.out.println("firing Missile"); }
@Override public void fire() { up(); super.fire(); } }
class Nbombecorator extends ADecorator { public Nbombecorator(APlane plane) { super(plane); }
public void down() { System.out.println("firing N-Bomb"); }
@Override public void fire() { super.fire(); down(); } }
public class test { public static void main(String[] args) { APlane aPlane = new Plane(); MissileDecorator decorator = new MissileDecorator(aPlane); Nbombecorator downDecorator = new Nbombecorator(decorator); downDecorator.fire(); } }
|
JavaScript
而在JavaScript中不需要实现各种抽象类和装饰器类,更多的是采用切面编程的思想实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Plane { fire() { console.log('firing ordinary bullets'); } }
Function.prototype.before = function (beforefn) { const _this = this return function () { beforefn.apply(this, arguments) return _this.apply(this, arguments) } } Function.prototype.after = function (afterfn) { const _this = this; return function () { const res = _this.apply(this, arguments); afterfn.apply(this, arguments); return res; } };
const plane = new Plane()
const decorator = plane.fire.before(() => { console.log('firing Missile'); }).after(() => { console.log('firing N-Bomb'); })
decorator()
|
AOP的其他应用
动态改变函数参数
假设我们有这样一个函数ajax,负责发送网络请求给后端。在一般情况下不需要传递token,但是在某些情况下需要将token字段传递给后端。
我们不想过多修改ajax内部的代码,因为我们想要保证ajax函数的纯净。那我们就可以在 before中动态注入参数。因为 before函数将 ·arguments·传递给了原来的函数,因此可以在 before中动态修改参数。
1 2 3 4 5 6 7 8
| function ajax(type, url, param) { console.log(param); }; ajax = ajax.before((type, url, param) => { param.token = '注入token' }) ajax('get', 'http:// xxx.com/userinfo', { name: 'sven' });
|
插件式的表单验证
我们在编写网页表单,需要判断输入项是否符合要求,比如用户名和密码是否为空:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <html>
<body> 用户名:<input id="username" type="text" /> 密码: <input id="password" type="password" /> <input id="submitBtn" type="button" value="提交"> </body> <script> const username = document.getElementById('username'), password = document.getElementById('password'), submitBtn = document.getElementById('submitBtn'); const formSubmit = function () { if (username.value === '') { return alert('用户名不能为空'); } if (password.value === '') { return alert('密码不能为空'); } const param = { username: username.value, password: password.value } } submitBtn.onclick = function () { formSubmit(); } </script>
</html>
|
如果验证项频繁改动,我们就需要频繁修改formSubmit函数的代码,而且校验逻辑和提交逻辑也耦合在了一起。因此我们需要将二者分离开来,在执行真正提交代码逻辑之前,执行校验逻辑。这里的validata函数也可以通过策略模式进行优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| Function.prototype.before = function (beforefn) { const _this = this return function () { const { isvaild,message } = beforefn.apply(this, arguments) if (isvaild) { return _this.apply(this, arguments) }else{ alert(message) } } }
const username = document.getElementById('username'), password = document.getElementById('password'), submitBtn = document.getElementById('submitBtn');
function validata() { if (username.value === '') { return { isvaild: false, message: '用户名不能为空' }; } if (password.value === '') { return { isvaild: false, message: '密码不能为空' }; } return { isvaild: true } }
let formSubmit = function () { const param = { username: username.value, password: password.value } console.log(param); } formSubmit = formSubmit.before(() => { return validata() }) submitBtn.onclick = function () { formSubmit(); }
|
优化的validata函数(参考JavaScript设计模式与开发实践(二)策略模式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| const strategies = { isNonEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, minLength: function (value, length, errorMsg) { if (value.length < length) { return errorMsg; } }, isMobile: function (value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } };
var Validator = function () { this.cache = []; };
Validator.prototype.add = function (dom, rule, errorMsg) { var ary = rule.split(':'); this.cache.push(function () { var strategy = ary.shift(); ary.unshift(dom.value); ary.push(errorMsg); return strategies[strategy].apply(dom, ary); }); };
Validator.prototype.start = function () { for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) { var msg = validatorFunc(); if (msg) { return msg; } } };
const validataFunc = function () { const validator = new Validator(); validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空'); validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位'); validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确'); const errorMsg = validator.start(); return errorMsg; }
const registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () { const errorMsg = validataFunc(); if (errorMsg) { alert(errorMsg); return false; } };
|