1. 模板模式的原理
模板模式,全称是模板方法设计模式,英文是 Template Method Design Pattern。模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
2. 模板模式的应用
2.1 复用
模板模式把一个算法中不变的流程抽象到父类的模板方法 templateMethod() 中,将可变的部分 method1()、method2() 留给子类 ContreteClass1 和 ContreteClass2 来实现。所有的子类都可以复用父类中模板方法定义的流程代码。
2.2 扩展
这里所说的扩展,并不是指代码的扩展性,而是指框架的扩展性,基于这个作用,模板模式常用在框架的开发中,让框架用户可以在不修改框架源码的情况下,定制化框架的功能。
3. 模板模式与 Callback 回调函数的区别和联系
回调能起到和模板模式相同的作用。
3.1 回调的原理解析
相对于普通的函数调用来说,回调是一种双向调用关系。A 类事先注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数的时候,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是“回调函数”。A 调用 B,B 反过来又调用 A,这种调用机制就叫作“回调”。
回调不仅可以应用在代码设计上,在更高层次的架构设计上也比较常用。比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的 URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。
3.2 模板模式 VS 回调
回调可以分为同步回调和异步回调(或者延迟回调)。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。从应用场景上来看,同步回调看起来更像模板模式,异步回调看起来更像观察者模式。
从应用场景上来看,同步回调跟模板模式几乎一致。它们都是在一个大的算法骨架中,自由替换其中的某个步骤,起到代码复用和扩展的目的。从代码实现上来看,回调和模板模式完全不同。回调基于组合关系来实现,把一个对象传递给另一个对象,是一种对象之间的关系;模板模式基于继承关系来实现,子类重写父类的抽象方法,是一种类之间的关系。
组合优于继承。在代码实现上,回调相对于模板模式会更加灵活,主要体现在下面几点:
- 像 Java 这种只支持单继承的语言,基于模板模式编写的子类,已经继承了一个父类,不再具有继承的能力。
- 回调可以使用匿名类来创建回调对象,可以不用事先定义类;而模板模式针对不同的实现都要定义不同的子类。
- 如果某个类中定义了多个模板方法,每个方法都有对应的抽象方法,那即便只用到其中的一个模板方法,子类也必须实现所有的抽象方法。而回调就更加灵活,我们只需要往用到的模板方法中注入回调对象即可。
3.3 回调的代码实现
type methodToCallback func()
type Invoker struct {
}
func (i *Invoker) Process(callBack methodToCallback) {
fmt.Println("before call back")
callBack()
fmt.Println("after call back")
}
// 回调使用方式
func TestInvoker(t *testing.T) {
invoker := &Invoker{}
invoker.Process(func() {
fmt.Println("call back todo")
})
invoker.Process(func() {
fmt.Println("now call back")
})
}
4. 模板模式的代码实现
type IOtp interface {
saveOTPCache(string)
getMessage(string) string
sendNotification(string) error
publishMetric()
}
type Otp struct {
iOtp IOtp
}
// 定义一个「算法」框架,具体算法由 IOtp 实现
func (o *Otp) GenAndSendOTP(optLength int) error {
otp := o.genRandomOTP(optLength)
o.iOtp.saveOTPCache(otp)
message := o.iOtp.getMessage(otp)
err := o.iOtp.sendNotification(message)
if err != nil {
return err
}
o.iOtp.publishMetric()
return nil
}
func (o *Otp) genRandomOTP(otpLength int) string {
rand.Seed(time.Now().UnixNano())
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
letterLength := len(letters)
randomRune := make([]rune, otpLength)
for i := 0; i < otpLength; i++ {
randomRune[i] = letters[rand.Intn(letterLength)]
}
return string(randomRune)
}
// Email golang 使用组合的方法实现继承,实现具体的步骤
type Email struct {
*Otp
}
func (s *Email) saveOTPCache(otp string) {
fmt.Printf("EMAIL: saving otp: %s to cache\n", otp)
}
func (s *Email) getMessage(otp string) string {
return "EMAIL OTP for login is " + otp
}
func (s *Email) sendNotification(message string) error {
fmt.Printf("EMAIL: sending Email: %s\n", message)
return nil
}
func (s *Email) publishMetric() {
fmt.Printf("EMAIL: publishing metrics\n")
}
// Sms golang 使用组合的方法实现继承,实现具体的步骤
type Sms struct {
*Otp
}
func (s *Sms) saveOTPCache(otp string) {
fmt.Printf("SMS: saving otp: %s to cache\n", otp)
}
func (s *Sms) getMessage(otp string) string {
return "SMS OTP for login is " + otp
}
func (s *Sms) sendNotification(message string) error {
fmt.Printf("SMS: sending Sms: %s\n", message)
return nil
}
func (s *Sms) publishMetric() {
fmt.Printf("SMS: publishing metrics\n")
}
// 客户端使用代码
func TestOtp(t *testing.T) {
sms := &Sms{}
email := &Email{}
otp := &Otp{}
/*
golang 不支持抽象类;
所以不能直接使用子类,要将子类注入到父类中;
父类中将接口作为其 field
*/
otp.iOtp = sms
err := otp.GenAndSendOTP(4)
if err != nil {
t.Fatal(err)
}
otp.iOtp = email
err = otp.GenAndSendOTP(4)
if err != nil {
t.Fatal(err)
}
}