模板模式实践--设计模式

在模板模式中,一个抽象类公开定义了执行他方法的模板,但是没有给出方法的实现。

方法的实现延迟到由继承的子类来实现。


抽象实现

然而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来说,模板模式应该是很常用的使用接口的方式了,但是并不是所有的使用接口的设计都是模板方式,本质区别在于模板模式会生成各种不同的类。

如果只是使用接口,可以看作是策略模式的一种。