GoLang設計模式16 – 模板方法模式

模板方法設計模式是一種行為型設計模式。這種模式通過常用於為某種特定的操作定義一個模板或者演算法模型。

以一次性密碼(OTP:One Time Password)為例。我們常見的一次性密碼有兩種:簡訊密碼(SMS OTP)或者郵件密碼(Email OTP)。不過不管是簡訊密碼還是郵件密碼,它們的處理步驟都是一樣的,步驟如下:

  1. 生成一串隨機字元串
  2. 將字元串保存進快取用來執行後續的驗證
  3. 準備通知內容
  4. 發送通知
  5. 記錄統計資訊

在以上的步驟中,除了第4項「發送通知」的具體方式不一樣,其他步驟都是不變的。即使以後有了新的一次性密碼發送方式,可以預見以上的步驟也是不變的。

在這類場景中,即一個操作的步驟是固定的,只是在具體的執行方式上存在差異,這時我們就可以用到模板方法模式了。在模板方法模式中,我們通常會為這個操作定義一個模板介面或演算法模型介面,介面中包含固定方法,然後由具體的實現類來重寫相關介面並實現這些操作。

下面是一次性密碼這個例子的實現:

otp.go

type iOtp interface {
	genRandomOTP(int) string
	saveOTPCache(string)
	getMessage(string) string
	sendNotification(string) error
	publishMetric()
}

type otp struct {
	iOtp iOtp
}

func (o *otp) genAndSendOTP(otpLength int) error {
	otp := o.iOtp.genRandomOTP(otpLength)
	o.iOtp.saveOTPCache(otp)
	message := o.iOtp.getMessage(otp)
	err := o.iOtp.sendNotification(message)
	if err != nil {
		return err
	}
	o.iOtp.publishMetric()
	return nil
}

簡單解讀下這段程式碼:

  • 在上面的程式碼中定義了iOtp介面,這個介面中的方法即OTP所需的相關步驟
  • 下面的smsemailiOtp介面的實現
  • 在結構體otp中定義了模板方法genAndSendOTP()

此外注意下:在上面這段程式碼中將iOtp介面和結構體otp合在了一起,提供了類似於抽象類的實現,這種做法大家需要的時候可以借鑒下。

sms.go

import "fmt"

type sms struct {
	otp
}

func (s *sms) genRandomOTP(len int) string {
	randomOTP := "1234"
	fmt.Printf("SMS: generating random otp %s\n", randomOTP)
	return randomOTP
}

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")
}

email.go

import "fmt"

type email struct {
	otp
}

func (s *email) genRandomOTP(len int) string {
	randomOTP := "1234"
	fmt.Printf("EMAIL: generating random otp %s\n", randomOTP)
	return randomOTP
}

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")
}

main.go

import "fmt"

func main() {
	smsOTP := &sms{}
	o := otp{
		iOtp: smsOTP,
	}
	o.genAndSendOTP(4)
	fmt.Println("")
	emailOTP := &email{}
	o = otp{
		iOtp: emailOTP,
	}
	o.genAndSendOTP(4)
}

輸出內容為:

SMS: generating random otp 1234
SMS: saving otp: 1234 to cache
SMS: sending sms: SMS OTP for login is 1234
SMS: publishing metrics

EMAIL: generating random otp 1234
EMAIL: saving otp: 1234 to cache
EMAIL: sending email: EMAIL OTP for login is 1234
EMAIL: publishing metrics

程式碼已上傳至GitHub: zhyea / go-patterns / template-method-pattern

END!!