前面我们讨论了Go语言中在没有继承的情况下,代理模式是如何实现的。但是少了继承之后,受影响的除了代理模式,还有今天我们要讨论的模板方法模式,我们知道的是,模板方法模式要求的是,我们在父类中实现一个方法调用的模板/流程,而不具体实现这些方法,留待子类去实现这些方法,然后我们实例化对象之后,调用这个父类的方法,父类的方法就会按照他定义好的流程去调用子代的方法,由此提高代码的复用性和扩展性。而Go中没有继承这个概念,我们该如何去实现Go中的模板方法模式呢。
我们知道Go使用组合替代掉了继承,那么我们能使用组合来实现模板方法模式吗?
1. 模板方法模式的实现(基于组合)
我们在一个类中定义好结构,然后用另一个类去组合它,然后外部调用这个类的模板方法:
type Beverage struct{}
func (b *Beverage) BoilWater() {
fmt.Println("Boiling water")
}
func (b *Beverage) Brew() {}
func (b *Beverage) PourInCup() {
fmt.Println("Pouring into cup")
}
func (b *Beverage) AddCondiments() {}
func (b *Beverage) PrepareRecipe() {
b.BoilWater()
b.Brew()
b.PourInCup()
b.AddCondiments()
}
type Coffee struct {
Beverage
}
func (c *Coffee) Brew() {
fmt.Println("Dripping coffee through filter")
}
func (c *Coffee) AddCondiments() {
fmt.Println("Adding sugar and milk")
}
我想实现的效果是调用对象的PrepareRecipe,然后RrepareRecipe调用Coffee的Brew方法,但是这样是不行的,毕竟组合不是继承,组合进去的对象,虽然我们可以用外部对象直接调用组合对象的方法,甚至也可以重写组合对象的方法,看起来就和继承一样,但是组合是不会使用外部对象定义的方法的。这也是为什么Go使用组合而不是继承的原因,继承破坏了一部分父类的封装,这导致我们在重写一些父类的方法的时候,必须清楚的明白这个方法在父类的作用,改编不是乱编,不考虑全面是会报莫名其妙的错的。
2. 模板方法模式(真)
言归正传,那么在Go中如何实现模板方法呢?
根据我找到的资料来看,可以定义一个模板类,在这个模板类中规定方法的框架,我们定义一个接口,这个接口定义了这个模板类需要用到的方法,然后我们实现这个接口,再用这个模板类包装我们的类。
我们将需要动态变化的类抽取出来变成一个接口,把方法的模板抽取出来作为一个类,然后Coffee实现这个接口,并将Coffee作为模板类的成员,如下:
type Beverage interface {
Brew()
AddCondiments()
}
type Drink struct {
Beverage
}
func (b *Drink) BoilWater() {
fmt.Println("Boiling water")
}
func (b *Drink) PourInCup() {
fmt.Println("Pouring into cup")
}
func (b *Drink) PrepareRecipe() {
b.BoilWater()
b.Brew()
b.PourInCup()
b.AddCondiments()
}
type Coffee struct {
Beverage
}
func (c *Coffee) Brew() {
fmt.Println("Dripping coffee through filter")
}
func (c *Coffee) AddCondiments() {
fmt.Println("Adding sugar and milk")
}
type Tea struct {
Beverage
}
func (t *Tea) Brew() {
fmt.Println("Steeping the tea")
}
func (t *Tea) AddCondiments() {
fmt.Println("Adding lemon")
}
然后实例化的时候传入一下接口的实现:
coffee := &Drink{&Coffee{}}
tea := &Drink{&Tea{}}
coffee.PrepareRecipe()
tea.PrepareRecipe()
以上 我们就实现了Go的模板方法模式,不过,既然都把框架单独抽取出来了,如果类只需要一个模板的话,我们也可以直接用一个方法来实现这种的模板方法(也许):
func DrinkPrepareRecipe(b Beverage){
fmt.Println("Boiling water")
b.Brew()
fmt.Println("Pouring into cup")
b.AddCondiments()
}