在模板模式中,一个抽象类公开定义了执行他方法的模板,但是没有给出方法的实现。
方法的实现延迟到由继承的子类来实现。
抽象实现
然而golang并没有继承,所以只能用组合接口的方式,来实现继承重写方法,使得父类能直接调用抽象出的方法。
这是golang的实现
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
| 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可以换个方式
1 2 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,时延等等数据
收集每秒的数据插入一个数据队列中,来获取最近一段时间值的最大值以及平均值
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
| 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
1 2 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上再组合一次
1 2 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来说,模板模式应该是很常用的使用接口的方式了,但是并不是所有的使用接口的设计都是模板方式,本质区别在于模板模式会生成各种不同的类。
如果只是使用接口,可以看作是策略模式的一种。