这篇文章我们还是继续我们的设计模式系列, 今天我们带来的一个全新的设计模式在实际开发中大家肯定都遇到过, 可能大家只是不知道它叫模板方法模式而已, 今天我们就来详细的说一下什么是模板方法模式,已经该模式如何运用.
至于什么是模板方法模式, 我们还是老规矩, 先来个定义, 然后上张类图更加直观的看一下.
定义
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤.
这个定义还是非常不错的, 至少认真读2遍还是可以理解什么意思的, 而且我们脑袋里可以想象到该如何设计这样的一个结构. 那它有什么样的使用场景呢? 其实在定义中已经说的很明白了, 大致有一下两点应用场景:
- 某些类别的算法中,实做了相同的方法,造成代码的重复.
- 控制子类别必须遵守的一些事项
上面两点总结起来说, 可以这样认为:
系统的组件都是按照一定的流程执行, 并且不同的组件的实现方式不同,需要我们延迟到子类来实现.
模板方法模式的关键点还是在将具体操作延迟到子类实现. 接下来, 我们再来看看类图, 从类图中我们可以更见直观的体会一下模板方法模式.
代码展示
按照惯例, 在这个环节中我们就应该用go语言来实现模板方法模式了, 不过由于go语言的特殊性, 它并没有实际意义上的继承, 所以在go中去实现, 还是和我们上面所说的有点不那么相同, 所以我决定这里我们先来用java实现一下模板方法模式, 有了了解之后, 我们再来考虑如何用go去实现.在今天这个例子中, 我们打算模拟一下男女生在出门的时候不同行为, 男生在出门之前可能就是在睡觉, 睡醒了爬起来就出去了; 而女生不一样了, 女生是要打扮一番才能出门的.
java版的模板方法模式
在有了上面的实际例子后, 我们就先来用java模拟一下. 首先我们需要定义一个接口, 用来规范这个行为.
interface IPerson {
void beforeOut();
void out();
}
有了接口, 就必须有实现, 不管男生还是女生, 他都是人, 而且在我们这个情景下, 出门这个行为是一样的, 不一样的只是出门之前的行为. 对应到代码中就是out
方法是男女生相同的, 不同的实现是beforeOut
方法, 所以我们在设计人这个类的时候之关心out
方法的实现.
abstract class Person implements IPerson {
private String name;
public Person(String name) {
this.name = name;
}
public void out() {
beforeOut();
System.out.println("go out...")
}
}
这里我们实现了out
方法, 并在 out
打印之前调用了beforeOut
方法, 至于这个方法如何实现, 这里要看是男生还是女生了, 很简单, 直接来看代码.
class Boy extends Person {
public Boy(String name) {
super(name);
}
public void beforeOut() {
System.out.println("get up...")
}
}
class Girl extends Person {
public Boy(String name) {
super(name);
}
public void beforeOut() {
System.out.println("dress up...")
}
}
ok, 不管是男生还是女生, 都继承自Person
这个抽象类, 并且实现了beforeOut
这个方法, 只不过男生的这个方法实现是一个get up, 而女生是一个dress up.
现在,一个简单的模板方法模式的应用我们就完成了, 从代码中我们也可以发现, 其实这个模式是很简单的, 在之前的博客中我就说过, 设计模式本身都很简单, 不太容易的是设计模式的应用和实践.
好了, 在通过java代码了解模板方法模式之后, 下面开始进入今天的重点了, 如何用go去实现呢?
golang版模板方法模式
对golang了解的同学都知道, 其实golang并没有严格意义上的继承机制, 它仅仅是利用组合的特性来模拟继承, 在这方法对于组合优于继承
体现的还是很好的, 不过这也带来了一些问题, 比如不能实现抽象方法, 上面的方法延迟实现等等. 说到这里大家不要着急, 虽然golang没有这个特性, 但是我们完全可以换一种思路来完成它, 毕竟模式是死的, 代码是活的, 那用什么方式实现呢? 答案就是绑定! 至于如何运用, 我不多说, 我们直接来看代码就行了!
根据上面的java代码, 我们来一一的实现它, 首先是接口, 在golang中我们也有接口, 所以, 我们可以设计一个如下的接口.
type IPerson interface {
SetName(name string)
BeforeOut()
Out()
}
这个接口和上面java版的大致相同, 主要的方法都在里面的, 接下来我们还是要设计一个Person
结构体. 这个Person
和上面的Person
可能不太一样, 我们先来看代码.
type Person struct {
Specific IPerson
name string
}
是有点不一样, 这里多了一个IPerson
类型的字段, 这个字段是干嘛的? 我们上面提过到, 这里我们准备用绑定的方式来实现模板方法模式, 这里的这个特殊的字段其实就是我们要绑定的实例, 在我们这个例子中就是男生或者女生了, 到这里可能很多人要迷惑了, 没关系, 最后我们在看使用的代码时就明白了, 这里我们还是先来看看这个Person
有什么方法吧.
func (this *Person) SetName(name string) {
this.name = name
}
func (this *Person) Out() {
this.BeforeOut()
fmt.Println(this.name + " go out...")
}
func (this *Person) BeforeOut() {
if this.Specific == nil {
return
}
this.Specific.BeforeOut()
}
第一个方法我们直接无视它, 第二个方法的实现和java版的其实是一样的, 来看看第三个方法, 这个方法是java版Person
没有去实现的, 按道理将这个BeforeOut
是要延迟到子类去实现的, 这个不符合国际惯例啊? 我们还是来看看这个方法的内容吧, 其实关键点就一句话, this.Specific.BeforeOut()
, 这句话直接调用了我们上面说的那个绑定的实例的BeforeOut
方法, 仔细品味一下, 还是符合惯例的, 这里我们只不过用了一种折中的方式来实现方法的延迟实现.
好了, 最难理解的地方我们详细说了, 下面就是男生和女生的各自实现了, 很简单, 直接上代码.
type Boy struct {
Person
}
func (_ *Boy) BeforeOut() {
fmt.Println("get up..")
}
type Girl struct {
Person
}
func (_ *Girl) BeforeOut() {
fmt.Println("dress up..")
}
代码中,不管是Boy
还是Girl
都匿名组合了Person
结构体, 他们的BeforeOut
方法的实现也和上面java版的大体相同, 这里我们就不再多说了, 最后我们来看看这样一个设计, 我们该如何使用.
func main() {
var p *Person = &Person{}
p.Specific = &Boy{}
p.SetName("qibin")
p.Out()
p.Specific = &Girl{}
p.SetName("loader")
p.Out()
}
这里代码的主体还是Person
, 只不过我们在区分男女生的时候, 给他指定了Specific
字段, 再来回想一下Person
结构体的BeforeOut
方法的实现, 是不是瞬间就明白怎么回事了. 最后我们再来看看运行结果.
最后还是关于文章的实例代码的事, 代码我都放github上了, 欢迎各种star, https://github.com/qibin0506/go-designpattern
有疑问加站长微信联系(非本文作者)