模板方法的核心在于对方法的重用,它将核心方法封装在抽象类中,让子类继承抽象类的方法,实现抽象类方法的共享,达到方法的共用,当然这种设计模式将导致抽象类控制子类必须遵守某些法则,这是一种行为的约束,但是为了让行为的约束更加可靠,抽象类中封装的方法通常是不变的算法,或者具有稳定的调用方式。
继承实现模板方法模式
子类继承的方法也可以扩展,要求对抽象类继承的方法进行重写
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| var Alert=function(data){ if(!data)return; this.content=data.content; this.panel=document.createElement('div'); this.contentNode=document.createElement('p'); this.confirmBtn=document.createElement('span'); this.closeBtn=document.createElement('b'); this.panel.className='alert'; this.closeBtn.className='a-close'; this.confirmBtn.className='a-confirm'; this.confirmBtn.innerHTML=data.confirm||'确认' this.contentNode.innerHTML=this.content; this.success=data.success||function(){}; this.fail=data.fail||function(){} } Alert.prototype={ init:function(){ this.panel.appendChild(this.closeBtn); this.panel.appendChild(this.contentNode); this.panel.appendChild(this.confirmBtn); document.body.appendChild(this.panel); this.bindEvent(); this.show(); }, bindEvent:function(){ var me=this; this.closeBtn.onclick=function(){ me.fail(); me.hide() } this.confirmBtn.onclick=function(){ me.success(); me.hide(); } }, hide:function(){ this.panel.style.display='none' }, show:function(){ this.panel.style.display='block' }
}
var RightAlert=function(data){ Alert.call(this,data); this.confirmBtn.className=this.confirmBtn.className+'right' }
RightAlert.prototype=new Alert();
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();
TitleAlert.prototype.init=function(){ this.panel.insertBefore(this.titleNode,this.panel.firstChild); Alert.prototype.init.call(this) }
var CancleAlert=function(data){ TitleAlert.call(this,data) this.cancel=data.cancel; this.cancelBtn=document.createElement('span'); this.cancelBtn.className='cancel' this.cancelBtn.innerHTML=this.cancel||'取消'
}
CancleAlert.prototype=new Alert() CancleAlert.prototype.init=function(){ TitleAlert.prototype.init.call(this); this.panel.appendChild(this.cancelBtn) } CancleAlert.prototype.bindEvent=function(){ var me=this; TitleAlert.prototype.bindEvent.call(me); this.cancelBtn.onclick=function(){ me.fail(); me.hide(); } } new CancleAlert({ title:'提示标题', content:'提示内容', success:function(){ console.log('ok') }, fail:function(){ console.log('cancel') } }).init()
|
JavaScript没有抽象类的缺点和解决方案
缺点:
- Js并没有从语法层面提供对抽象类的支持,抽象类的第一个作用是隐藏对象具体类型,由于Js是一门“类型模糊”的语言,所以隐藏对象的类型在Js中并不重要
- 另一方面,当我们在Js中使用原型继承模拟传统的类式继承,并没有编译器帮助我们进行任何形式的检查,我们也没有办法保证子类会重写父类的“抽象方法”,在Java中编译器会保证子类重写父类中的抽象方法,但是Js并没有进行这些检查工作,当我们在编写代码时得不到任何形式的警告,完全寄托于程序员的记忆力是很危险的,特别是当我们使用模板方法模式这种完全依赖继承而实现的设计模式时。
解决方案:
- 使用鸭子类型模拟接口检查,确保子类中确实重写了父类的方法,但模拟接口检查会带来不必要的复杂性,而且要求程序员主动进行接口检查,就要求在业务代码中添加一些和业务逻辑无关的代码
- 让Beverage.prototype.brew等方法直接抛出一个异常,如果因为粗心忘记编写Coffee.prototype.brew方法,那么至少我们会在程序运行时得到一个错误
1 2 3 4 5 6
| Beverage.prototype.brew = function(){ throw new Error('子类必须重写父类的brew方法') } Beverage.prototype.pourInCup = function(){ throw new Error('子类必须重写父类的pourInCup方法') }
|
第二种方法实现简单,付出的额外代价小,缺点是我们得到错误信息的时间点太靠后
我们一共有3中机会可以得到这个错误信息,第一次是在编写代码的时候,通过编译器检查来得到错误信息,第二次是在创建对象的时候用鸭子类型进行“接口检查”,最后一次是在程序运行过程中
用高阶函数实现模板继承
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
| let Beverage = function(param){ let boilWater = function(){ console.log("把水煮沸") } let brew = param.brew || function(){ throw new Error('必须传递brew方法') } let pourInCup = param.pourInCup || function(){ throw new Error('必须传递pourInCup方法') } let addCondiments = param.addCondiments || function(){ throw new Error('必须传递addCondiments方法') } let F = function(){} F.prototype.init = function(){ boilWater() brew() pourInCup() addCondiments() } return F; } let Coffee = Beverage({ brew:function(){ console.log('用沸水冲泡咖啡') }, pourInCup:function(){ console.log('把咖啡倒进杯子') }, addCondiments:function(){ console.log('加糖和牛奶') } }) let coffee = new Coffee() coffee.init()
|
当我们把brew,pourInCup,addCondiments这些方法一次传入Beverage函数,Beverage函数被调用之后返回构造器F,F类中包含了“模板方法” F.prototype.init,跟继承得到的效果一样,该“模板方法依旧封装了子类的算法框架