Well, Go Design Pattern Combat series, a golang series of design pattern business real use.
The whole series is here
preface
This series focuses on how to use design patterns in our real business scenarios.
This series of articles mainly adopts the following structure:
- What is “XX design pattern”?
- What real business scenarios can use the “XX design pattern”?
- How to use “XX design pattern”?
This article focuses on how the template pattern can be used in real business scenarios.
What is a “template pattern”?
Abstract methods are defined in an abstract class where the execution steps of the algorithm and the concrete algorithm are defined, as well as the algorithm that may change. Different subclasses inherit the abstract class and implement the abstract methods of the parent class.
Advantages of the template pattern:
- Invariant algorithms are inherited and reused: invariant parts are highly encapsulated and reused.
- Change algorithm subclass inheritance and concrete implementation: change part of the subclass only need to concrete implementation of the abstract part, easy to expand, and can be infinite expansion.
What real-world business scenarios can use the “template pattern”?
All scenarios that meet the following requirements:
The steps performed by the algorithm are stable, but some specific algorithms may have different scenarios.
For example, if you boil noodles, you must first boil water, and then put noodles in. The above process is called the process of boiling noodles. The steps of this cooking process are constant, but the way of boiling water may be different in different environments. Some people may boil water with natural gas, some with induction hobs, some with wood fire, and so on. We can draw the following conclusions:
Process of cooking noodles
The step of is stableProcess of cooking noodles
The method of boiling water is variable
What real business scenarios can we use “template patterns” for?
For example, the lottery interface of the lottery system, why:
- The steps of the lottery are stable and invariant -> invariant algorithm execution steps
- Different sweepstakes type activities may be handled differently in certain logical ways -> change certain algorithms
How to use “template mode”?
The four steps I’ve outlined for using design patterns can be used rote:
- The business card
- Business flow chart
- Code modeling
- The demo code
The business card
I have sorted out the general text process of the drawing interface of the following lottery business according to the real business needs through various drawing scenes (red envelope rain, candy rain, Whac-a-gopher, big wheel (nine squares), testing eyesight, answering questions, playing games, paying scratch-off, scoring scratch-off, etc.) that I have been in contact with in history.
Understand the specific business please click on the demand analysis of general drawing tools | SkrShop”
The main steps | Main logic | Draw type | substeps | The child logic |
---|---|---|---|---|
1 | Verify whether the activity number (serial_NO) exists and obtain activity information | – | – | – |
2 | Verify that activities and sessions are in progress | – | – | – |
3 | Verification of other parameters (Different activity types are implemented differently) | – | – | – |
4 | Check the number of lucky draws (deduct at the same time) | – | – | – |
5 | Whether the activity requires consumption points | – | – | – |
6 | Check the number of lucky draw (deduct at the same time) | – | – | – |
7 | Get the prize information | – | – | – |
8 | Get node prize information (Different activity types are implemented differently) | By time draw type | 1 | Do nothing(draw the prize of this game, no other logic) |
8 | Draw type by number of draws | 1 | Judge is the user’s number of lottery | |
8 | 2 | Obtain the prize information of the corresponding node | ||
8 | 3 | Duplicate all the original prize information (extract the prize of this node node) | ||
8 | Lucky draw according to the amount range | 1 | Determine which amount range you belong to | |
8 | 2 | Obtain the prize information of the corresponding node | ||
8 | 3 | Duplicate all the original prize information (extract the prize of this node node) | ||
9 | Lucky draw | – | – | – |
10 | Prize quantity judgment | – | – | – |
11 | Assemble prize information | – | – | – |
Note: The process may not be completely accurate
Conclusion:
Main logic
It’s stableVerification of other parameters
andGet node award information
The algorithm is variable
Business flow chart
We obtained the following business flow chart through the combed text business process:
Code modeling
Through the above analysis, we can get:
An abstract class-concrete common method 'Run', which defines the algorithm's execution steps - concrete private methods, concrete methods that do not change - abstract methods, Method subclass 1 (according to time lottery type) - inherit abstract class parent class - implement abstract method subclass 2 (according to the number of lottery lottery type) - inherit abstract class parent class - implement abstract method subclass 3 (according to the amount range range lottery) - inherit abstract class parent class - implement abstract methodCopy the code
However, golang does not have the concept of inheritance, so we can convert the dependency on the abstract method in the abstract class to the dependency on the abstract method in the interface. We can also inherit templates using compositing multiplexing:
Abstract BehaviorInterface 'BehaviorInterface' (including the following methods to implement) - checkParams' method for other parameters - getPrizesByNode 'method for obtaining node prize information - concrete common method' Run ', Inside specific private methods to define the steps of the algorithm - ` checkParams ` logic inside the actual dependent interface BehaviorInterface. CheckParams (CTX) abstract methods - specific private methods ` getPrizesByNode ` The logic inside the actual dependent interface BehaviorInterface. GetPrizesByNode (CTX) abstract methods - other specific private method, Specific methods of BehaviorInterface BehaviorInterface 1 (lottery type by time) - Implement interface method BehaviorInterface 2 (lottery type by lotteries) - Implement interface method Implement structure 3 of the BehaviorInterface (lottery by amount range) - implement the interface methodCopy the code
We also get our UML diagram:
The demo code
package main
import (
"fmt"
"runtime"
)
//------------------------------------------------------------
// My code has no 'else' series
// Template mode
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
const (
// ConstActTypeTime draw type by time
ConstActTypeTime int32 = 1
// ConstActTypeTimes
ConstActTypeTimes int32 = 2
// ConstActTypeAmount is drawn in a range of numbers
ConstActTypeAmount int32 = 3
)
// Context Context
type Context struct {
ActInfo *ActInfo
}
// ActInfo context
type ActInfo struct {
// Draw type 1: time draw 2: Times draw 3: range draw
ActivityType int32
// Other fields are omitted
}
// BehaviorInterface abstracts the behavior of different lottery types
type BehaviorInterface interface {
// Check other parameters (different activity types implement different)
checkParams(ctx *Context) error
// Get node prize information (different activity types implement different)
getPrizesByNode(ctx *Context) error
}
// TimeDraw specifies the lottery behavior
// Lucky draw type by time such as red envelope rain
type TimeDraw struct{}
// checkParams
func (draw TimeDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "By time draw type: Special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw TimeDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Do nothing...")
return
}
// TimesDraw specific draw behavior
// According to the number of lucky draw type such as answer the question
type TimesDraw struct{}
// checkParams
func (draw TimesDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Draw by number of draw type: special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw TimesDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. Determine how many times the user has drawn the lottery...")
fmt.Println(runFuncName(), 2. Get the prize information of the corresponding node...)
fmt.Println(runFuncName(), "3. Duplicate all the original prize information (extract the prize of this node)...")
return
}
// AmountDraw specifies the raffle behavior
// Draw a lucky draw according to the amount range such as the order amount
type AmountDraw struct{}
// checkParams
func (draw *AmountDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Raffle by amount range: special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw *AmountDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. Determine which amount range...")
fmt.Println(runFuncName(), 2. Get the prize information of the corresponding node...)
fmt.Println(runFuncName(), "3. Duplicate all the original prize information (extract the prize of this node)...")
return
}
// Lottery template
type Lottery struct {
// Abstract behavior of different lottery types
concreteBehavior BehaviorInterface
}
// Run lottery algorithm
// Static algorithm steps
func (lottery *Lottery) Run(ctx *Context) (err error) {
// Check whether the activity number (serial_NO) exists and obtain the activity information
iferr = lottery.checkSerialNo(ctx); err ! =nil {
return err
}
// Check whether the event is in progress
iferr = lottery.checkStatus(ctx); err ! =nil {
return err
}
// "Abstract method" : check other parameters
iferr = lottery.checkParams(ctx); err ! =nil {
return err
}
// Specific method: activity lottery times check (deduct at the same time)
iferr = lottery.checkTimesByAct(ctx); err ! =nil {
return err
}
// Specific method: whether the activity needs to consume points
iferr = lottery.consumePointsByAct(ctx); err ! =nil {
return err
}
// Specific method: check (deduct at the same time)
iferr = lottery.checkTimesBySession(ctx); err ! =nil {
return err
}
// Specific method: obtain the information of the race prize
iferr = lottery.getPrizesBySession(ctx); err ! =nil {
return err
}
// "abstract method" : Get node prize information
iferr = lottery.getPrizesByNode(ctx); err ! =nil {
return err
}
// Specific method: lucky draw
iferr = lottery.drawPrizes(ctx); err ! =nil {
return err
}
// Specific method: determine the number of prizes
iferr = lottery.checkPrizesStock(ctx); err ! =nil {
return err
}
// Specific method: assemble the prize information
iferr = lottery.packagePrizeInfo(ctx); err ! =nil {
return err
}
return
}
// checkSerialNo Check activity number (serial_NO) exists
func (lottery *Lottery) checkSerialNo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Verify the existence of activity number (Serial_NO) and obtain activity information...")
// Get activity information pseudocode
ctx.ActInfo = &ActInfo{
// Assume that the current activity type is sweepstakes
ActivityType: ConstActTypeTimes,
}
// Get the specific behavior of the current lottery type
switch ctx.ActInfo.ActivityType {
case 1:
// Draw by time
lottery.concreteBehavior = &TimeDraw{}
case 2:
// Draw according to the number of lucky draws
lottery.concreteBehavior = &TimesDraw{}
case 3:
// Draw a lucky draw according to the amount range
lottery.concreteBehavior = &AmountDraw{}
default:
return fmt.Errorf("Non-existent activity types.")}return
}
// checkStatus Whether the checkactivity and field are in progress
func (lottery *Lottery) checkStatus(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check whether activities, sessions are ongoing...")
return
}
// checkParams
// The algorithm for different scenarios is converted to dependency abstraction
func (lottery *Lottery) checkParams(ctx *Context) (err error) {
// An abstract method of the interface that is actually dependent on
return lottery.concreteBehavior.checkParams(ctx)
}
// checkTimesByAct Activity draw times check
func (lottery *Lottery) checkTimesByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check the number of lucky draws...")
return
}
// Whether the consumePointsByAct activity requires credits
func (lottery *Lottery) consumePointsByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Does the activity require consumption points...")
return
}
// checkTimesBySession activity lottery check times
func (lottery *Lottery) checkTimesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check the number of lucky draws...")
return
}
// getPrizesBySession to get the prize information
func (lottery *Lottery) getPrizesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Get information about the tournament prizes...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
// The algorithm for different scenarios is converted to dependency abstraction
func (lottery *Lottery) getPrizesByNode(ctx *Context) (err error) {
// An abstract method of the interface that is actually dependent on
return lottery.concreteBehavior.getPrizesByNode(ctx)
}
/ / drawPrizes draw
func (lottery *Lottery) drawPrizes(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Lottery...")
return
}
// checkPrizesStock number of prizes
func (lottery *Lottery) checkPrizesStock(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Judging the number of prizes...")
return
}
// packagePrizeInfo Assembly prize information
func (lottery *Lottery) packagePrizeInfo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Assemble the prize information...")
return
}
func main(a) {
(&Lottery{}).Run(&Context{})
}
// Get the name of the running function
func runFuncName(a) string {
pc := make([]uintptr.1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
}
Copy the code
Here is the code execution result:
[Running] go run "... / easy - tips/go/SRC/patterns/template/template. Go "main. (* the Lottery). CheckSerialNo calibration activity number (serial_no) exists, and get the information... Main.(*Lottery). CheckStatus Checks whether the activity is ongoing... Main. TimesDraw. CheckParams by lottery number lottery type: special parameters calibration... Main.(*Lottery). CheckTimesByAct... Main.(*Lottery). ConsumePointsByAct activity whether to spend credits... Main.(*Lottery). CheckTimesBySession... Main.(*Lottery). GetPrizesBySession... main.TimesDraw.getPrizesByNode 1. Judge is the user's number of lottery... main.TimesDraw.getPrizesByNode 2. Get the prize information of the corresponding node... main.TimesDraw.getPrizesByNode 3. Duplicate all the original prize information (extract the prize of the node)... Main. (* the Lottery). DrawPrizes draw... Main.(*Lottery). CheckPrizesStock... Main.(*Lottery). PackagePrizeInfo...Copy the code
Demo code address: github.com/TIGERB/easy…
Code Demo2 (using Golang’sSynthesis of reuse
Feature implementation)
package main
import (
"fmt"
"runtime"
)
//------------------------------------------------------------
// My code has no 'else' series
// Template mode
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
const (
// ConstActTypeTime draw type by time
ConstActTypeTime int32 = 1
// ConstActTypeTimes
ConstActTypeTimes int32 = 2
// ConstActTypeAmount is drawn in a range of numbers
ConstActTypeAmount int32 = 3
)
// Context Context
type Context struct {
ActInfo *ActInfo
}
// ActInfo context
type ActInfo struct {
// Draw type 1: time draw 2: Times draw 3: range draw
ActivityType int32
// Other fields are omitted
}
// BehaviorInterface abstracts the behavior of different lottery types
type BehaviorInterface interface {
// Check other parameters (different activity types implement different)
checkParams(ctx *Context) error
// Get node prize information (different activity types implement different)
getPrizesByNode(ctx *Context) error
}
// TimeDraw specifies the lottery behavior
// Lucky draw type by time such as red envelope rain
type TimeDraw struct {
// composite templates
Lottery
}
// checkParams
func (draw TimeDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "By time draw type: Special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw TimeDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Do nothing...")
return
}
// TimesDraw specific draw behavior
// According to the number of lucky draw type such as answer the question
type TimesDraw struct {
// composite templates
Lottery
}
// checkParams
func (draw TimesDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Draw by number of draw type: special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw TimesDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. Determine how many times the user has drawn the lottery...")
fmt.Println(runFuncName(), 2. Get the prize information of the corresponding node...)
fmt.Println(runFuncName(), "3. Duplicate all the original prize information (extract the prize of this node)...")
return
}
// AmountDraw specifies the raffle behavior
// Draw a lucky draw according to the amount range such as the order amount
type AmountDraw struct {
// composite templates
Lottery
}
// checkParams
func (draw *AmountDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Raffle by amount range: special parameter check...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
func (draw *AmountDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. Determine which amount range...")
fmt.Println(runFuncName(), 2. Get the prize information of the corresponding node...)
fmt.Println(runFuncName(), "3. Duplicate all the original prize information (extract the prize of this node)...")
return
}
// Lottery template
type Lottery struct {
// Abstract behavior of different lottery types
ConcreteBehavior BehaviorInterface
}
// Run lottery algorithm
// Static algorithm steps
func (lottery *Lottery) Run(ctx *Context) (err error) {
// Check whether the activity number (serial_NO) exists and obtain the activity information
iferr = lottery.checkSerialNo(ctx); err ! =nil {
return err
}
// Check whether the event is in progress
iferr = lottery.checkStatus(ctx); err ! =nil {
return err
}
// "Abstract method" : check other parameters
iferr = lottery.checkParams(ctx); err ! =nil {
return err
}
// Specific method: activity lottery times check (deduct at the same time)
iferr = lottery.checkTimesByAct(ctx); err ! =nil {
return err
}
// Specific method: whether the activity needs to consume points
iferr = lottery.consumePointsByAct(ctx); err ! =nil {
return err
}
// Specific method: check (deduct at the same time)
iferr = lottery.checkTimesBySession(ctx); err ! =nil {
return err
}
// Specific method: obtain the information of the race prize
iferr = lottery.getPrizesBySession(ctx); err ! =nil {
return err
}
// "abstract method" : Get node prize information
iferr = lottery.getPrizesByNode(ctx); err ! =nil {
return err
}
// Specific method: lucky draw
iferr = lottery.drawPrizes(ctx); err ! =nil {
return err
}
// Specific method: determine the number of prizes
iferr = lottery.checkPrizesStock(ctx); err ! =nil {
return err
}
// Specific method: assemble the prize information
iferr = lottery.packagePrizeInfo(ctx); err ! =nil {
return err
}
return
}
// checkSerialNo Check activity number (serial_NO) exists
func (lottery *Lottery) checkSerialNo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Verify the existence of activity number (Serial_NO) and obtain activity information...")
return
}
// checkStatus Whether the checkactivity and field are in progress
func (lottery *Lottery) checkStatus(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check whether activities, sessions are ongoing...")
return
}
// checkParams
// The algorithm for different scenarios is converted to dependency abstraction
func (lottery *Lottery) checkParams(ctx *Context) (err error) {
// An abstract method of the interface that is actually dependent on
return lottery.ConcreteBehavior.checkParams(ctx)
}
// checkTimesByAct Activity draw times check
func (lottery *Lottery) checkTimesByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check the number of lucky draws...")
return
}
// Whether the consumePointsByAct activity requires credits
func (lottery *Lottery) consumePointsByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Does the activity require consumption points...")
return
}
// checkTimesBySession activity lottery check times
func (lottery *Lottery) checkTimesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Check the number of lucky draws...")
return
}
// getPrizesBySession to get the prize information
func (lottery *Lottery) getPrizesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Get information about the tournament prizes...")
return
}
// getPrizesByNode get node prize information (different activity types implement different)
// The algorithm for different scenarios is converted to dependency abstraction
func (lottery *Lottery) getPrizesByNode(ctx *Context) (err error) {
// An abstract method of the interface that is actually dependent on
return lottery.ConcreteBehavior.getPrizesByNode(ctx)
}
/ / drawPrizes draw
func (lottery *Lottery) drawPrizes(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Lottery...")
return
}
// checkPrizesStock number of prizes
func (lottery *Lottery) checkPrizesStock(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Judging the number of prizes...")
return
}
// packagePrizeInfo Assembly prize information
func (lottery *Lottery) packagePrizeInfo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "Assemble the prize information...")
return
}
func main(a) {
ctx := &Context{
ActInfo: &ActInfo{
ActivityType: ConstActTypeAmount,
},
}
switch ctx.ActInfo.ActivityType {
case ConstActTypeTime: // Draw by time type
instance := &TimeDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
case ConstActTypeTimes: // Draw according to the number of lucky draws
instance := &TimesDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
case ConstActTypeAmount: // Draw a lucky draw according to the amount range
instance := &AmountDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
default:
/ / an error
return}}// Get the name of the running function
func runFuncName(a) string {
pc := make([]uintptr.1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
}
Copy the code
Here is the code execution result:
[Running] go run "... / easy - tips/go/SRC/patterns/template/templateOther go "main. (* the Lottery). CheckSerialNo calibration activity number (serial_no) exists, and get the information... Main.(*Lottery). CheckStatus Checks whether the activity is ongoing... Main.(*AmountDraw).checkParams draw by amount range range: special parameter check... Main.(*Lottery). CheckTimesByAct... Main.(*Lottery). ConsumePointsByAct activity whether to spend credits... Main.(*Lottery). CheckTimesBySession... Main.(*Lottery). GetPrizesBySession... main.(*AmountDraw).getPrizesByNode 1. Determine which amount range... main.(*AmountDraw).getPrizesByNode 2. Get the prize information of the corresponding node... main.(*AmountDraw).getPrizesByNode 3. Duplicate all the original prize information (extract the prize of the node)... Main. (* the Lottery). DrawPrizes draw... Main.(*Lottery). CheckPrizesStock... Main.(*Lottery). PackagePrizeInfo...Copy the code
Demo2 code address: github.com/TIGERB/easy…
conclusion
In conclusion, the core of the abstract process of “template pattern” is to grasp the invariability and change:
- The same:
Run
Raffle steps in the method ->Inherited reuse
- Change: Different scenes ->
Be concretized
checkParams
Parameter checking logicgetPrizesByNode
The logic to get the prize for the node
Special note: 1. There is no 'else' in my code. It is just a result that is naturally infinitely close to or achieved under the condition of reasonable design of the code. 2. The concepts of some design patterns in this series may be different from the original concepts, because they will be used in combination with the actual situation, and their essence will be selected for appropriate changes and flexible use.Copy the code
Go design pattern practice series for more articles click here to view