模板方法模式在Ext中的应用

216 阅读6分钟

定义

设计模式是一套可以被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且提高代码的可靠性。

分类(23种)

1. 创建型(用于创建对象):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式
2. 结构型(用于处理类或对象的组合):适配器模式、装饰者模式、代理模式、外观模式、桥梁模式、组合模式、享元模式
3. 行为型(用于描述对类或对象怎样交互和怎样分配职责):策略模式、模版方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

入门书籍推荐

《Head First Design Pattern》---鸭子书

设计模式原则

  1. 单一职责原则:一个方法只做一件事情(函数的功能单一,使用较多)

     //bad
     class Animal {
         constructor (animal) {
             this.animal = animal;
         }
         behavior () {
             console.log(this.animal + ':开始吃饭~~~');
             console.log(this.animal + ':开始睡觉~~~');
             console.log(this.animal + ':开始游泳~~~');
         }
     }
    
     new Animal('乌龟').behavior();
     new Animal('燕子').behavior();
     
    
     //good
     class Animal {
         constructor (animal) {
             this.animal = animal;
         }
         behavior () {
             console.log(this.animal + ':开始吃饭~~~');
             console.log(this.animal + ':开始睡觉~~~');
         }
         extraBehavior () {
             console.log(this.animal + ':开始游泳~~~');
         }
     }
    
     let animal1 = new Animal('燕子').behavior();
     let animal2 = new Animal('乌龟');
     animal2.behavior();
     animal2.extraBehavior();
     
    
  2. 里氏替换原则:所有引用基类的地方必须能透明地使用其子类对象(对继承的约束,js是弱类型语言,使用较少)

  3. 依赖倒置原则:面向接口编程,依赖于抽象而不依赖于具体实现(如类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成,可以将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,降低修改类A的几率,使用较少,js没有接口的概念)

  4. 接口隔离原则:把大的接口拆分成小的接口(功能单一,有点类似单一职责,js中没有接口的概念,使用较少)

  5. 迪米特法则:一个对象应该对其他对象保持最少的了解(降低类与类之间的耦合)

  6. 开闭原则:面向扩展开放,面向修改关闭(增加需求时,扩展新代码,而非修改已有代码,使用较多)

      //bad
      class Animal  {
         constructor (animal) {
             this.animal = animal;
         }
         behavior () {
             console.log(this.animal + ':开始吃饭~~~');
             console.log(this.animal + ':开始睡觉~~~');
             if (this.animal === '乌龟') {
                 console.log(this.animal + ':开始游泳~~~');
             }
         }
     }
     new Animal('乌龟').behavior();
    
     //good
     class Animal  {
         constructor (animal) {
             this.animal = animal;
         }
         behavior () {
             console.log(this.animal + ':开始吃饭~~~');
             console.log(this.animal + ':开始睡觉~~~');
         }
     }
     class Nekton extends Animal {
         constructor (animal) {
             super (animal);
         }
         behavior () {
             super.behavior();
             console.log(this.animal + ':开始游泳~~~');
         }
     }
     new Nekton('乌龟').behavior();
     
     
    

Ext中的设计模式

1. 组合模式:Ext使用组合模式来管理组件,组件可以嵌套组合使用;如:Ext.Container可以通过add()方法添加组件,通过get(id)方法获取组件,通过remove(comp)方法删除组件。

2. 观察者模式:Ext.util.Observable(addListener,removeListener,fireEvent)

3. 代理模式:
var store = new Ext.data.Store({
  proxy:{...}
})

4. 单例模式:创建单例窗口
5. 模板方法模式:Ext中的组件渲染继承内部实现(具体看后面举例)

模版方法模式在Ext中的使用

Ext组件有很深的继承关系,很多方法和流程存在重复,回忆一下Ext的组件生命周期

image.png

创建一个Panel:

触发beforeRender事件 -> 拿到父容器 -> 设置rendered=true -> 调用onRender方法 -> 设置Panel的css -> 触发render事件 -> 调用afterRender -> 根据hidden属性确定是否隐藏Panel -> doLayout布局设置Panel的位置

创建Panel的子类tabPanel:

触发beforeRender事件 -> 拿到父容器 -> 设置rendered=true -> 调用onRender方法 -> 设置Panel的css -> 触发render事件 -> 调用afterRender -> 根据hidden属性确定是否隐藏Panel -> doLayout布局设置Panel的位置 -> 设置activeTab

我们可以看到只有最后一步是不同的

模版方法模式在Ext中的使用

不仅仅是继承:继承是属性和方法的继承,一个流程,一系列工序的"继承"——-模板方法模式

模板方法模式:

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

应用模板方法后:

创建tabPanel:

1. 完成Panel渲染  
2. 设置activeTab

创建Panel:

1. 完成Ext.Container的渲染

创建Ext.Container:

1. 完成Ext.Component的渲染  
2. doLayout布局设置panel的位置

创建Ext.Component:

1. 触发beforeRender事件 
2. 拿到父容器 
3. 设置rendered=true  
4. 调用onRender方法  
5. 设置这个panel的css 
6. 触发render事件 
7. 调用aferRender 
8. 根据用户设置的hidden属性确定是否要隐藏panel

整个流程达到了很好的复用和简化

流程的跳转的控制实现:call和apply

如Panel中的initComponent中有this.callParent(arguments),表示让panel去执行Container里面的initComponent方法; Container的initComponent中也存在this.callParent(arguments),这里的this还是原来那个panel,然后让panel再去执行Container父类里面的initComponent方法………

例子

封装一个弹出框组件,统一弹出框的样式:

弹出框类型:

1.基本弹出框
2.包含标题的弹出框
3.带有确定取消按钮的弹出框
4.带有输入框的弹出框
...

开发和扩展流程:

1.创建一个基础弹出框(这里以一个带有内容和确定取消按钮的弹出框作为基本弹出框的骨架)

2.创建一个带有标题的弹出框

3.创建一个带有输入框的弹出框

/*基本提示框*/

    let Alert = function (data = {}) {
        //提示框面板
        this.box = document.createElement('div');
        //提示内容
        this.contentNode = document.createElement('h5');
        //确定按钮
        this.confirmBtn = document.createElement('span');
        //关闭按钮
        this.closeBtn = document.createElement('span');
        //提示框面板类名
        this.box.className = 'alert';
        //提示内容类名
        this.contentNode.className = 'content';
        //关闭按钮类名
        this.closeBtn.className = 'close';
        //确定按钮类名
        this.confirmBtn.className = 'confirm';
        //确定按钮文字内容
        this.confirmBtn.innerText = data.confirm || '确认';
        //取消按钮文字内容
        this.closeBtn.innerText = data.close || '关闭';
        //提示内容文本
        this.contentNode.innerText = data.content || '提示';
        //确认按钮回调
        this.success = data.success || function () {};
        //关闭按钮回调
        this.cancel = data.cancel || function () {};
    }
    Alert.prototype = {
        init: function () {
            //把各部分组件放到页面
            this.box.appendChild(this.contentNode);
            this.box.appendChild(this.closeBtn);
            this.box.appendChild(this.confirmBtn);
            document.body.appendChild(this.box);
            //绑定点击事件
            this.bindEvent();
        },
        bindEvent: function () {
            let me = this;
            this.closeBtn.onclick = function () {
                me.cancel();
                me.hide()
            };
            this.confirmBtn.onclick = function () {
                me.success();
                me.hide();
            }
        },
        hide: function () {
            this.box.style.display = 'none';
        },
        show: function () {
            this.box.style.display = 'block';
        },
        alert: function () {
            this.init();
        }
    }


/*带有标题的提示框*/

    let TitleAlert = function (data = {}) {
        //继承基本弹框函数
        Alert.call(this, data);
        //创建标题面板
        this.titleNode = document.createElement('h3');
        //设置标题样式类
        this.titleNode.className = 'title';
        //设置标题内容
        this.titleNode.innerText = data.title || '标题';
    }

    //继承基本提示框的方法
    TitleAlert.prototype = new Alert();
    //创建带标题提示框的方法扩展
    TitleAlert.prototype.init = function () {
        this.box.insertBefore(this.titleNode, this.firstChild);
        //继承基本提示框的init方法
        Alert.prototype.init.call(this);
    },
    //创建显示方法扩展
    TitleAlert.prototype.alert = function () {
        //继承基本提示框的alert方法
        Alert.prototype.alert.call(this);
    }

/*带有输入框的提示框*/

    let Prompt = function (data = {}) {
        //继承带有标题输入框
        TitleAlert.call(this, data);
        //创建输入框
        this.inputNode = document.createElement('input');
        //给输入框添加样式
        this.inputNode.className = 'input';
    }
    //继承带有标题输入框的方法
    Prompt.prototype = new TitleAlert();
    //创建可输入提示框的方法扩展
    Prompt.prototype.init = function () {
        this.box.insertBefore(this.inputNode, this.firstChild);
        //继承带有标题输入框的init方法
        TitleAlert.prototype.init.call(this);
    },
    //创建显示方法扩展
    Prompt.prototype.prompt = function () {
        //继承基本提示框的alert方法
        TitleAlert.prototype.alert.call(this);
    }
    //扩展新的拿input内容的方法
    Prompt.prototype.getValue = function () {
        let value = this.inputNode.value;
        return value || '';
   }
    
    

模板方法总结

模板方法的核心在于方法的重用,它将核心方法封装在基类中,让子类继承基类的方法,达到方法的共享,子类也可以基于基类进行扩展。