美文网首页
JavaScript设计模式 | 16-模板方法模式

JavaScript设计模式 | 16-模板方法模式

作者: 夏海峰 | 来源:发表于2019-02-28 14:25 被阅读16次

1、模式定义

模板方法模式,在父类中定义一组操作算法的骨架,而将算法的一些实现步骤延迟到子类中去实现,使得子类可以在不改变父类算法结构的同时,又可以重新定义算法中的某些实现逻辑。

模板方法模式,就是把多个模型抽象化归一,从中抽象提取出一个最基本的模板,然后让其它模块继承这个模板方法,即可实现并拓展出风格类似但又各具特色的子模块。

这如同蛋糕店中各式各样的蛋糕,这些蛋糕事实上都共用了一个相同的“模具”。

2、模板方法模式 举例

使用模板方法模式,实现一个弹框组件。如下是 Alert基类:

// 定义一个弹框基类
var Alert = function(data) {
    if (!data) return;
    // 弹框中主体内容
    this.content = data.content;
    this.panel = document.createElement('div');
    this.panel.className = 'alert';
    this.contentNode = document.createElement('p');
    this.contentNode.innerHTML = this.content;
    // 确认按钮
    this.confirmBtn = document.createElement('span');
    this.confirmBtn.className = 'alert-confirm';
    this.confirmBtn.innerHTML = data.confirm || '确认';
    // 关闭按钮
    this.closeBtn = document.createElement('b');    
    this.closeBtn.className = 'alert-close';
    // 弹框确认事件
    this.handleConfirm = data.confirm || function() {
        // default do something
    };
    // 弹框关闭事件
    this.handleClose = data.close || function() {
        // default do something
    }
}

// 定义Alert基类的原型方法
Alert.prototype = {
    // 初始化
    init: function() {
        // 组装弹框中的元素
        this.panel.appendChild(this.contentNode);
        this.panel.appendChild(this.confirmBtn);
        this.panel.appendChild(this.closeBtn);
        // 把弹框插入到页面中
        document.body.appendChild(this.panel);
        // 绑定事件
        this.bindEvent();
        // 显示弹框
        this.show();
    },
    bindEvent: function() {
        var that = this;
        this.closeBtn.onclick = function () {
            that.handleClose();
            that.hide();
        }
        this.confirmBtn.onclick = function() {
            that.handleConfirm();
            that.hide();
        }
    },
    show: function() {
        this.panel.style.display = 'block';
    },
    hide: function() {
        this.panel.style.display = 'none';
    }
}

基于这个 Alert 模板,我们来扩展一个改变了按钮样式的 RightAlert 弹框类。扩展如下:

var RightAlert = function(data) {
    Alert.call(this, data);
    // 扩展确认按钮的样式
    this.confirmBtn.className = this.confirmBtn.className + ' btn-right';
}
RightAlert.prototype = new Alert();

基于这个 Alert 模板,我们再扩展一个带有弹框标题的 TitleAlert 弹框,扩展如下:

var TitleAlert = function(data) {
    Alert.call(this, data);
    // 增加一个弹框标题
    this.title = data.title;
    this.titleNode = document.createElement('h3');
    this.titleNode.innerHTML = this.title;
}
TitleAlert.prototype = new Alert();
// 扩展 Alert的 init方法
TitleAlert.prototype.init = function() {
    // 向弹框面板中插入标题节点
    this.panel.insertBefore(this.titleNode, this.panel.firstChild);
    Alert.prototype.init.call(this);
}

继续扩展,基于带标题的弹框 TitleAlert 扩展一个带“取消”按钮的弹框。扩展如下:

var CancelAlert = function(data) {
    TitleAlert.call(this, data);
    // 增加一个“取消按钮”
    this.cancelText = data.cancelText;
    this.cancelBtn = document.createElement('span');
    this.cancelBtn.innerHTML = this.cancelText || '取消';
}
CancelAlert.prototype = new Alert();
CancelAlert.prototype.init = function() {
    TitleAlert.prototype.init.call(this);
    this.panel.appendChild(this.cancelBtn);
}
CancelAlert.prototype.bindEvent = function() {
    var that = this;
    TitleAlert.prototype.bindEvent.call(this);
    this.cancelBtn.onclick = function() {
        that.handleClose();
        that.hide();
    }
}

如此使用模板方法模式实现弹框组件,不仅结构功能样式统一,而且便于日后升级迭代,还便于扩展出其它类型的弹框。如下创建一个CancelAlert的弹框:

new CancelAlert({
    title: '弹框标题',
    content: '弹框内容',
    confirm: function {
        console.log('确认');
    },
    close: function() {
        console.log('取消');
    }
}).init();

3、模板方法模式 再举例

下面使用模板方法模式,实现一个导航组件。Nav基类如下:

// 导航组件 基类
var Nav = function(data) {
    this.item = '<a href="{#href#}" title="{#title#}">{#name#}</a>';
    this.html = '';
    for (var i=0; i<data.length; i++) {
        this.html += formatString(this.item, data[i]);
    }
    return this.html;
}

// 工具方法:把模板字符串中的插值符号替换成实际的文本
function formatString(str, data) {
    return str.replace(/\{#(\w+)#\}/g, function(match, key) {
        return typeof data[key] === undefined ? '' : data[key];
    });
}

基于Nav基类,扩展出一种带有数字角标的导航组件。实现如下:

var NumNav = function(data) {
    // 添加数据角标装饰
    var tpl = '<b>{#num#}</b>';
    for (var i=data.length-1; i>= 0; i--) {
        data[i].name += data[i].name + formatString(tpl, data[i]);
    }
    // 继承Nav基类,并返回字符串
    return Nav.call(this, data);
}

基于Nav基类,扩展出一种带有网址的导航组件。实现如下:

var LinkNav = function (data) {
    var tpl = '<span>{#link#}</span>';
    for (var i=data.length-1; i>=0; i--) {
        data[i].name += data[i].name + formatString(tpl, data[i]);
    }
    // 继承Nav基类,并返回字符串
    return Nav.call(this, data);
}

下面以NumNav为例创建对象,测试一下:

var nav = document.getElementById('content');
var data = [
    {
        href: 'http://www.geekxia.cn',
        title: '点击我查看更多信息!',
        name: '动态',
        num: '10'
    },
    {
        href: 'http://www.geekxia.cn',
        title: '点击我查看更多信息!',
        name: '新闻',
        num: '20'
    },
    {
        href: 'http://www.geekxia.cn',
        title: '点击我查看更多信息!',
        name: '关于',
        num: '30'
    }
];
nav.innerHTML = NumNav(data);

4、小结

模板方法模式的核心在于对方法的重用,它将核心方法封装在基类中,让子类继承基类的方法,实现对基类方法的共享。这是一种行为的约束。

子类所继承到的方法也可以进一步扩展,这就要求对基类继承的方法进行重写。当然为了更好地实践,我们通常要控制这种扩展,这样才能让基类对子类有更稳健的束缚力。然而子类对自身私有行为的扩展还是很有必要的。


本章完 2019-02-28

相关文章

网友评论

      本文标题:JavaScript设计模式 | 16-模板方法模式

      本文链接:https://www.haomeiwen.com/subject/htihuqtx.html