设计模式之-装饰器模式

装饰器模式

1.核心:

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式比生成子类更加灵活

2.装饰器模式定义:

这是一种结构型设计模式,允许你将对象(A)放入包含行为的特殊对象里面,从而为原来的对象(A)绑定新的行为

3.本质

因此装饰器的本质,其实就是组合,而非继承。

4.简单的一个表单装饰器demo,个人感觉更像是套娃哈哈

javascript 复制代码
// 表单类
class Form {
    submit(){
        console.log('表单提交啦');
    }
}
// 装饰器基类
class FormDecorator {
    constructor(form){
        this.form=form;
    }
    submit(){
        this.form.submit();
    }
}
// 校验装饰器
class ValidatorDecorator extends FormDecorator{
    submit(){
        this.validate();
        super.submit();
    }
    validate(){
        console.log('表单校验开始了');
    }
}
// 日志装饰器
class LogDecorator extends FormDecorator{
    submit(){
        super.submit();
        this.log();
    }
    log(){
        console.log('表单日志记录打印了');
    }
}
// 使用
let form = new Form();
form = new ValidatorDecorator(form);
form = new LogDecorator(form);
form.submit();

5.场景

假设你现在需要一个有通知功能的库,其他程序可以用你这个库来发送一些重要的通知。

一开始的设计就是一个 Notifier 类,里面有几个成员变量:

构造函数

一个 send 方法:通过发送邮件的方式来发送通知

typescript 复制代码
interface INotifier {
    send(message:string):void;
}
// 基础的通知器:只能发送邮件通知
class Notifier implements INotifier {
    private emailAddresses:string[]=[];
    constructor(emails:string[]){
        this.emailAddresses = emails;
    }
    send(message: string): void {
       // 模拟发送邮件
       console.log(`Email sent to ${this.emailAddresses.join(", ")}: ${message}`)
    }
}
// 使用
const baseNotifier = new Notifier(["123@qq.com", "456@gmail.com"]);
baseNotifier.send("hello,world!");

目前库的功能是 OK 的,但是需求变化了!

许多客户端希望通过不同的方式来收到通知,例如通过发送短信、微信、QQ,那么此时我们就会去扩展 Notifier 这个类,创建一些子类

好像上面的需求也能够完美的解决。

但是,需求又变化了!

有些客户期望多个渠道收到通知,比如有客户希望微信和 QQ 同时收到通知,有客户端希望短信和微信收到通知...

这个时候你就会发现,如果还是按照之前的设计,那么子类的数量会成倍的增加:

像这种场景下,最佳的解决方案是使用组合而非继承,这也是前面在介绍设计原则的时候,其中就提到了组合优于继承。

此时我们就可以使用装饰器模式,新的结构如下:

typescript 复制代码
// 定义整个装饰器的框架,具体的装饰器需要继承这个类
interface INotifier {
    send(message:string):void;
}
// 基础的通知器:只能发送邮件通知
class Notifier implements INotifier {
    private emailAddresses:string[]=[];
    constructor(emails:string[]){
        this.emailAddresses = emails;
    }
    send(message: string): void {
       // 模拟发送邮件
       console.log(`Email sent to ${this.emailAddresses.join(", ")}: ${message}`)
    }
}
// 基础装饰器
class BaseDecorator implements INotifier {
    // 这个基础的装饰器类,是不能够单独使用的
    private wrappee:INotifier;
    // 回头初始化的时候,需要传入一个 INotifier 类型的实例
    // 就是接收具体的渠道(QQ、微信、SMS)
    constructor(notifier:INotifier){
        this.wrappee = notifier;
    }
    send(message:string):void {
        this.wrappee.send(message);
    }
}
// QQ装饰器
class QQDecorator extends BaseDecorator {
    private QQID:string;
    constructor(notifier:INotifier,QQID:string){
        super(notifier);
        this.QQID = QQID;
    }
    // 部分重写父类send方法
    send(message:string):void{
        // 调用父类的send方法
        super.send(message);
        // 在发送完消息之后,在发送一条QQ消息
        console.log(`QQ message sent to ${this.QQID}: ${message}`);
    }
}
// SMS装饰器
class SMSDecorator extends BaseDecorator {
    private phoneNumber:string;
    constructor(notifier:INotifier,phoneNumber:string){
        super(notifier);
        this.phoneNumber=phoneNumber;
    }
    // 部分重写父类send方法
    send(message:string):void{
        // 调用父类的send方法
        super.send(message);
        // 再调用完父类的 send 方法之后,再完成子类扩展的逻辑
        // 在发送完消息之后,在发送一条SMS消息
        console.log(`SMS message sent to ${this.phoneNumber}: ${message}`);
    }
}
// 微信装饰器
class WeChatDecorator extends BaseDecorator {
    private weChatId:string;
    constructor(notifier:INotifier,weChatId:string){
        super(notifier);
        this.weChatId = weChatId;
    }
    // 部分重写父类的 send 方法
    send(message: string): void {
        // 调用父类的 send 方法
        super.send(message);
        // 在发送完消息之后,再发送一条微信消息
        console.log(`WeChat message sent to ${this.weChatId}: ${message}`);
  }
}

// 使用
const baseNotifier = new Notifier(["123@qq.com", "456@gmail.com"]);
baseNotifier.send("hello,world!");
console.log("==================================");
// 接下来需求有变:客户希望发送短信以及微信通知
const smsNotifier = new SMSDecorator(baseNotifier,'1243443553');
const smsAndWechatNotifier = new WeChatDecorator(smsNotifier,"mingtianguohou");
smsAndWechatNotifier.send('这是一条非常重要的信息');
console.log("==================================");
// 回头需求又有变换,客户希望在增加QQ通知
const allTypeNotifier = new QQDecorator(smsAndWechatNotifier,"1051705661");
allTypeNotifier.send("这是另外一条非常重要的消息,所有渠道都会收到")
  1. AOP的应用实例,体验装饰函数的威力
javascript 复制代码
 	<button tag="login" id="button">点击打开登陆浮层</button>
    <script>
        var showLogin = function(){
            console.log('打开登陆浮层')
            log(this.getAttribute('tag'));
        }
        var log=function(tag){
            console.log('上报标签为:'+tag);
            // 真正上报代码略。。
        }
        document.getElementById('button').onclick = showLogin
    </script>

现在我们看到在showLogin函数里,即要负责打开登陆浮层,又要负责数据上报,这是两个层面的功能,在此处却被耦合在一个函数,使用AOP分离之后代码如下

javascript 复制代码
	<button tag="login" id="button">点击打开登陆浮层</button>
    <script>
        Function.prototype.after = function(afterfn){
            var _self=this;
            return function(){
                var ret = _self.apply(this,arguments);
                afterfn.apply(this,arguments);
                return ret;
            }
        }
        var showLogin = function(){
            console.log('打开登陆浮层');
        }
        var log=function(){
            console.log('上报标签为:'+this.getAttribute('tag'));
            // 真正上报代码略。。
        }
        showLogin = showLogin.after(log)
        document.getElementById('button').onclick = showLogin
    </script>

7.再看一个AOP的例子

javascript 复制代码
		//现在有一个用于发起ajax请求的函数,这个函数负责项目中所有的ajax异步请求
        var ajax=function(type,url,param){
            console.log(param);
            // 发送ajax请求代码略
        }
        ajax('get','http://xxx.com/userinfo',{name:'syt'});
        // 上面的伪代码表示向后台cgi和合作也很愉快。直到有一天,
        // 我们的网站遭受了CSRF攻击,解决这个最简单的办法是在HTTP请求中带上一个Token参数
        var getToken = function(){
            return 'token';
        }
        // 现在的任务是给每个ajax请求都加上Token参数
        var ajax = function(type,url,param){
            param = param || {};
            param.Token = getToken();
            // 发送ajax请求代码略...
        }
        //虽然已经解决了问题,但是我门的ajax函数变得相对僵硬了,
        // 每个从ajax函数里发出的请求都自动带上了Token参数,虽然现在没有什么问题,
        // 但如果将来把这个函数移植到其他项目,或者把他放到一个开源库中供其他人使用,Token参数都将是多余的。
        // 为了解决这个问题,先把ajax函数还原成一个干净的函数
        Function.prototype.before = function(beforefn){
            var _self=this;
            return function(){
                beforefn.apply(this,arguments);
                return _self.apply(this,arguments);
            }
        }
        var ajax=function(type,url,param){
            console.log(param);
            // 发送ajax请求代码略
        }
        var getToken = function(){
            return 'token';
        }
        ajax = ajax.before(function(type,url,param){
            param.Token=getToken();
        });
        ajax('get','http://xxx.com/userinfo',{name:'syt'});
        // 明显可以看到,用AOP的方式给ajax函数动态装饰上了Token函数,
        // 保证了ajax函数是个相对纯净的函数,提高了ajax函数的可服用行,他在被迁往其他项目时,不需要做任何修改

非原创,来源渡一谢杰老师和javascript设计模式与开发实践 -曾探

相关推荐
看见繁华3 小时前
C++ 设计模式&设计原则
java·c++·设计模式
清水白石0083 小时前
《Python 装饰器模式与代理模式深度剖析:从语法技巧到架构实战》
python·代理模式·装饰器模式
雨中飘荡的记忆7 小时前
观察者模式:从理论到生产实践
java·设计模式
阿波罗尼亚8 小时前
Head First设计模式(十二) 设计原则 复合模式
设计模式
老朱佩琪!8 小时前
Unity原型模式
开发语言·经验分享·unity·设计模式·原型模式
拾忆,想起8 小时前
设计模式三大分类完全解析:构建高质量软件的基石
xml·微服务·设计模式·性能优化·服务发现
老朱佩琪!8 小时前
Unity装饰器设计模式
unity·设计模式
syt_10139 小时前
设计模式之-策略模式
设计模式·bash·策略模式
老鼠只爱大米9 小时前
Java设计模式之代理模式(Proxy)深度解析
java·设计模式·代理模式·proxy pattern·java设计模式·proxypattern