
Template Method を Go で
Template Method は、 振る舞いに関するデザインパターンの一つで、 アルゴリズムの骨組みを基底クラスで定義し、 サブクラスではアルゴリズムの全体的な構造は残したまま、 ステップを上書きします。
概念的な例
One Time Password (OTP) 機能を考えてみましょう。 使い切りパスワードをユーザーに配布するには、 SMS、 電子メールなどいくつかの方法があります。 しかし、 SMS であろうと電子メールであろうと、 OTP の全体的プロセスは一緒です:
- n 桁の乱数を発生。
- 後の検証のため、 この番号をキャッシュに保存。
- 通知内容を用意。
- 通知を送信。
将来導入されるかも知れない新型 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