0%

模板方法模式

模板方法的核心在于对方法的重用,它将核心方法封装在抽象类中,让子类继承抽象类的方法,实现抽象类方法的共享,达到方法的共用,当然这种设计模式将导致抽象类控制子类必须遵守某些法则,这是一种行为的约束,但是为了让行为的约束更加可靠,抽象类中封装的方法通常是不变的算法,或者具有稳定的调用方式。

继承实现模板方法模式

子类继承的方法也可以扩展,要求对抽象类继承的方法进行重写

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
//模板类 基础提示框data渲染数据
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;
//点击确定按钮执行方法 如果data中有success方法则为success方法,否则为空函数
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);
//为确认按钮添加right类
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);
//继承基本提示框的init方法
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,跟继承得到的效果一样,该“模板方法依旧封装了子类的算法框架