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 noodlesThe step of is stable
  • Process of cooking noodlesThe 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 logicIt’s stable
  • Verification of other parametersandGet node award informationThe 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 reuseFeature 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:RunRaffle steps in the method ->Inherited reuse
  • Change: Different scenes ->Be concretized
    • checkParamsParameter checking logic
    • getPrizesByNodeThe 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