Template method design pattern is a behavioral design pattern. This pattern is commonly used to define a template or algorithm model for a specific operation.
Take OTP (One Time Password) as an example. There are two common one-time passwords: SMS password (SMS OTP) or email password (Email OTP). However, the processing steps for both SMS password and email password are the same, as follows:
- Generate a random string
- Save the string into the cache for subsequent validation
- Prepare notification content
- Send notification
- Record statistics
In the above steps, except that the specific method of "sending notice" in Item 4 is different, other steps remain unchanged. Even if there is a new one-time password sending method in the future, it can be predicted that the above steps will remain unchanged.
In such scenarios, that is, the steps of an operation are fixed, but there are differences in the specific execution methods. At this time, we can use the template method mode. In the template method mode, we usually define a template interface or algorithm model interface for this operation. The interface contains fixed methods, and then the specific implementation class rewrites the relevant interfaces and implements these operations.
The following is the implementation of the one-time password example:
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 }
Briefly interpret this Code:
- The iOtp interface is defined in the above code. The methods in this interface are the relevant steps required by OTP
- The following sms and email are the implementation of iOtp interface
- The template method genAndSendOTP() is defined in the structure otp
In addition, note that in the above code, the iOtp interface and the structure otp are combined to provide an implementation similar to an abstract class. This approach can be used for reference when you need it.
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) }
The output content is:
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
Code uploaded to GitHub: zhyea / go-patterns / template-method-pattern
END!!