春のセール
Template Method

Template Method を Go で

Template Method 振る舞いに関するデザインパターンの一つで アルゴリズムの骨組みを基底クラスで定義し サブクラスではアルゴリズムの全体的な構造は残したまま ステップを上書きします

概念的な例

One Time Password OTP 機能を考えてみましょう 使い切りパスワードをユーザーに配布するには SMS 電子メールなどいくつかの方法があります しかし SMS であろうと電子メールであろうと OTP の全体的プロセスは一緒です

  1. n 桁の乱数を発生
  2. 後の検証のため この番号をキャッシュに保存
  3. 通知内容を用意
  4. 通知を送信

将来導入されるかも知れない新型 OTP もおそらくこれらのステップを踏むものと思われます

というわけで 我々は ある特定の作業のステップの大枠は同じだが ステップの実装は異なる という状況にあります これは Template Method パターンを適用するのに適した状況です

最初に 決まった数のメソッドからなる 基底の雛形のアルゴリズムを定義します これが 我々のテンプレート・メソッドです 次にステップのメソッドを実装しますが テンプレート・メソッドはそのままにします

otp.go: テンプレート・メソッド

package main

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

// type otp struct {
// }

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

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
	}
	return nil
}

sms.go: 具象実装

package main

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
}

email.go: 具象実装

package main

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
}

main.go: クライアント・コード

package main

import "fmt"

func main() {
	// otp := otp{}

	// smsOTP := &sms{
	// 	otp: otp,
	// }

	// smsOTP.genAndSendOTP(smsOTP, 4)

	// emailOTP := &email{
	// 	otp: otp,
	// }
	// emailOTP.genAndSendOTP(emailOTP, 4)
	// fmt.Scanln()
	smsOTP := &Sms{}
	o := Otp{
		iOtp: smsOTP,
	}
	o.genAndSendOTP(4)

	fmt.Println("")
	emailOTP := &Email{}
	o = Otp{
		iOtp: emailOTP,
	}
	o.genAndSendOTP(4)

}

output.txt: 実行結果

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

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

他言語での Template Method

Template Method を C# で Template Method を C++ で Template Method を Java で Template Method を PHP で Template Method を Python で Template Method を Ruby で Template Method を Rust で Template Method を Swift で Template Method を TypeScript で