在模板模式中,一个抽象类公开定义了执行他方法的模板,但是没有给出方法的实现。
方法的实现延迟到由继承的子类来实现。
抽象实现
然而golang并没有继承,所以只能用组合接口的方式,来实现继承重写方法,使得父类能直接调用抽象出的方法。
这是golang的实现
| 12
 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
 
 | package main
 import (
 "fmt"
 )
 
 const (
 typeA = iota
 typeB
 )
 
 type report interface {
 report()
 }
 
 type AbstractClass struct {
 r            report
 }
 
 func newAbstractClass(t int) (*AbstractClass){
 c := &AbstractClass{}
 switch t{
 case typeA:
 c.r = &concreteClassA{}
 case typeB:
 c.r = &concreteClassB{}
 }
 return c
 }
 
 func (this *AbstractClass) Report() {
 this.r.report()
 this.r.report()
 this.r.report()
 }
 
 func NewA() (*AbstractClass) {
 return newAbstractClass(typeA)
 }
 
 type concreteClassA struct {
 }
 
 func (this *concreteClassA) report() {
 fmt.Println("A")
 }
 
 func NewB() (*AbstractClass) {
 return newAbstractClass(typeB)
 }
 
 type concreteClassB struct {
 }
 
 func (this *concreteClassB) report() {
 fmt.Println("B")
 }
 
 func main() {
 a := NewA()
 a.Report()
 b := NewB()
 b.Report()
 }
 
 | 
在这个例子中,AbstractClass是父类,定义了模板方法Report()是调用子方法report()三次
concreteClassA和concreteClassB分别实现了抽象方法report(),输出了A和B的字符
为了封装NewA和NewB分别使用不同的接口,封装了newAbstractClass来完成对象的创建过程。
然而这并没有完全达到模板模式的目的,对模板模式来说,继承得到的类可以添加其他方法,因为得到是不同的类
这里NewA可以换个方式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | func NewA() (*ConcreteClassA){a := &ConcreteClassA{}
 a.AbstractClass = newAbstractClass(typeA)
 return a
 }
 
 type ConcreteClassA struct {
 *AbstractClass
 }
 
 func (this *ConcreteClassA) Report() {
 this.AbstractClass.Report()
 }
 
 | 
B同理,这样A和B就是单独的类,可以分别实现其他方法
实际实现
举个例子,某个信息收集的库,需要收集CPU,内存,磁盘,QPS,时延等等数据
收集每秒的数据插入一个数据队列中,来获取最近一段时间值的最大值以及平均值
| 12
 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
 
 | type Value struct {
 Name        string
 Value        float64
 }
 
 type Values []*Value
 
 type stat interface{
 stat()    Values
 }
 
 type queue interface{
 init()
 insert(Values)
 GetAverage(uint32) Values
 GetMax(uint32) Values
 }
 
 type Stat struct {
 queue
 s                stat
 }
 
 func newStat(ctx context.Context, statName int, args string) (s *Stat, err error){
 s = &Stat{}
 switch statName {
 case diskUtil:
 s.s,err = newDiskStat(args)
 case netUtil:
 s.s,err = newNetStat(args)
 case loadavgUtil:
 s.s,err = newLoadavgStat(args)
 case qps:
 s.s,err = newQpsStat(args)
 case delay:
 s.s,err = newDelayStat(args)
 default:
 err = fmt.Errorf("invalid stat %s",statName)
 }
 if err != nil {
 return
 }
 s.queue.init()
 go s.housekeeper(ctx)
 return
 }
 
 func (this *Stat) housekeeper(ctx context.Context) {
 t := time.NewTicker(time.Second)
 for ;; {
 values := this.s.stat()
 this.queue.insert(values)
 select {
 case <-t.C:
 case <-ctx.Done():
 t.Stop()
 return
 }
 }
 }
 
 | 
这是一个典型的模板模式。而和其他不同的时,DelayStat作为一个特殊的种类,需要有额外的一些方法
他需要的GetAverage和GetMax返回的不是Values类型而是DelayValues
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | type DelayValue struct {MethodName        string
 Values            []*SectionValue
 }
 
 type SectionValue struct {
 Section            time.Duration
 Value            float64
 }
 
 type DelayValues []*DelayValue
 
 | 
所以DelayStat是在Stat上再组合一次
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | type DelayStat struct {*Stat
 }
 
 func NewDelayStat(ctx context.Context, args string) (ds *DelayStat, err error) {
 ds = &DelayStat{}
 ds.Stat, err = newStat(ctx, delay, args)
 return
 }
 
 func (this *DelayStat) GetAverage(count uint32) (vs DelayValues){
 ...
 }
 
 func (this *DelayStat) GetMax(count uint32) (vs DelayValues){
 ...
 }
 
 | 
总结
对golang来说,模板模式应该是很常用的使用接口的方式了,但是并不是所有的使用接口的设计都是模板方式,本质区别在于模板模式会生成各种不同的类。
如果只是使用接口,可以看作是策略模式的一种。