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

原创内容,转载请注明出处

Posted by Weakyon Blog on August 31, 2017

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

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


抽象实现

然而golang并没有继承,所以只能用组合接口的方式,来实现继承重写方法,使得父类能直接调用抽象出的方法。

这是golang的实现

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可以换个方式

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,时延等等数据

收集每秒的数据插入一个数据队列中,来获取最近一段时间值的最大值以及平均值


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

type DelayValue struct {
	MethodName		string
	Values			[]*SectionValue
}

type SectionValue struct {
	Section			time.Duration
	Value			float64
}

type DelayValues []*DelayValue

所以DelayStat是在Stat上再组合一次

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

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

31 Aug 2017