模板模式定义算法骨架,使用上有两个特征,一是要继承算法骨架,达到复用的目的;二是具体的算法步骤在子类中实现,达到扩展的目的。
UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/19template.go
1.定义
1.1模板模式
模板模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
UML:
1.2分析
模板模式的UML图几乎是最简单的了。
**模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。**TemplateMethod是算法骨架,PrimitiveOperation1和PrimitiveOperation2是骨架中的某些步骤。
在模板模式经典的实现中,模板方法定义为 final,可以避免被子类重写。需要子类重写的方法定义为 abstract,可以强迫子类去实现。
以前用这种定义好算法骨架,具体实现在不同子类的方案时,一般使用的是工厂方法加代理模式。工厂方法能够提供更多的灵活性,但如果一个算法骨架中有10个具体算法,总不能让工厂生产10个不同的对象吧。所以如果算法骨架中有多个具体算法,而这些算法又是高内聚的,用模板模式就很合适。
2.使用场景
业务开发场景中,模板模式使用频率并不高,但是在框架方面,还是使用的比较频繁的。
先查看了Gin源码Gin源码剖析,发现用的不是模板模式,其实完全没用啥设计模式,算是用了里氏替换原则。
主框架有Handler接口,用于做路由解析、对应逻辑执行与返回。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Gin中的engin实现该接口
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
之所以说没有使用模板模式,主要是因为并没有继承算法框架。
后来想想Go语言没有继承,没法实现经典款模板方法。虽然可以用组合实现模板方法,但无法控制对算法框架的重写、也无法强迫子类重写算法实现,感觉价值不大。有很多更好的方案能够实现目的。
3.代码实现
虽然没什么特别好的case,但是代码还是要写一下的。参考大话设计模式,写一个试卷场景吧。试卷内容确定,但试卷答案不定。
package main
import "fmt"
/**
* @Author: Jason Pang
* @Description: 试卷
*/
type Examination struct {
//函数变量,回答问题1
Answer1 func()
//函数变量,回答问题2
Answer2 func()
}
/**
* @Author: Jason Pang
* @Description: 问题列表,也是算法骨架
* @receiver e
*/
func (e *Examination) Questions() {
fmt.Println("第一题:谁是最帅的人?")
e.Answer1()
fmt.Println("第二题:生活的意义是什么?")
e.Answer2()
}
/**
* @Author: Jason Pang
* @Description: 真正做试卷
*/
type ExamplationDo struct {
Examination
}
/**
* @Author: Jason Pang
* @Description: 写答案1
* @receiver d
*/
func (d *ExamplationDo) Answer1() {
fmt.Println("答案:我自己")
}
/**
* @Author: Jason Pang
* @Description: 写答案2
* @receiver d
*/
func (d *ExamplationDo) Answer2() {
fmt.Println("答案:躺平")
}
func main() {
e := &ExamplationDo{}
//需要对父类函数进行赋值
e.Examination.Answer1 = e.Answer1
e.Examination.Answer2 = e.Answer2
e.Questions()
}
输出:
➜ myproject go run main.go
第一题:谁是最帅的人?
答案:我自己
第二题:生活的意义是什么?
答案:躺平
上面的代码使用一些技巧才能实现模板模式。父类Examination实现了算法骨架,同时包含两个函数变量Answer1和Answer2,代表算法实现。子类ExamplationDo实现了Answer1和Answer2,并将这两个函数赋值给父类的函数变量。
总结
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
最后
大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)
我的个人博客为:https://shidawuhen.github.io/