preface
Look at the code “documentation” and learn design patterns
The design modes explained on the Internet are based on examples of real life scenes, but as a coder, it is necessary to have the transformation thinking of converting real life scenes to code realization scenes, so I think it is easy to understand the design mode, but difficult to practice into the corresponding code scenes.
So our code art series will explain design patterns in the way of coding live!
Generator mode
Look at the concept:
Generator pattern is a creative design pattern, also known as builder pattern. It abstracts the object creation step into a generator and controls the sequence of all generation steps through a directing class (director). The client uses the guide class and passes in the corresponding generator, and the corresponding object can be obtained through the interface of the guide class.
The intent of the Builder design pattern is to separate the construction of a complex object from its representation. By doing so the same construction process can create different representations. The construction of a complex object is separated from its representation so that the same construction process can create different representations
To summarize: Some objects have the same creation process, but because of their own characteristics, the creation process needs to be separated from the personalized properties when creating them.
I barely know what that means. Cool! Continue to look at the structure
Look at the structure:
- The Builder interface declares product construction steps common to all type generators.
- Concrete Builders provide different implementations of the construction process. Concrete generators can also construct products that do not follow a common interface.
- Products are the final generated objects. Products built by different generators need not belong to the same class hierarchy or interface.
- The Director class defines the order in which construction steps are invoked so that you can create and reuse specific product configurations.
- The Client must associate a generator object with the master class. In general, you only need to make a one-time correlation with the parameters of the main class constructor. The master class can then use the generator object for all subsequent construction tasks.
Practical learning
Do you understand the conceptual description? Don’t understand on the right, programmers understand the concept first than directly on the code to stimulate.
Here is the purpose of our foreword:
- Don’t use life scenarios, use product requirements.
- Don’t use graphic descriptions, use readable code.
In actual combat ~
Product demand
Programmers without product thinking are not good sellers. This need is a myth
PM to add quotation function in their own e-commerce website computer products under the category.
PRD describes that when the user goes to the MAC brand details page, he can choose the configuration of I7 CPU, 500GB ram, 1TB disk, and view the quote.
Different brands of parts have different prices, and different brands have different discounts at the same time.
Requirement objective: Calculate the discounted price of the computer configuration selected by the user in real time.
Demand revenue: Increased order rate by 50%
Technical documentation
It’s techno-brain time!
At first glance, it sounds simple, with no complicated logic.
It’s really simple. But the problem is that different brands of components have different prices, and different brands of discounts are also different. The user selected A,B,C-Z mess configuration, should I write my code like this?
Var cpuPrice map[string]float {XXX: 100, XXXX: 200} float {XXX: 100, XXXX: 200} // Who dares to use this function? getMaxPrice(type= '', cpu='',mem='',ram='',disk=''... {if type == 'MAC' {price := 0 if(CPU == "default"){price += cpuPrice[CPU] if(mem == ""){mem=" default"} price += memPrice[mem] ... } elseif (type == 'huawei') { ... }}Copy the code
This is a lot of code, and most of it is duplicate code, and as the computer gets more and more configurations, the getMaxPrice function gets more and more arguments. Who can guarantee the order of the arguments? Some are required to fill in some are not required to fill in, how to help the required is not omitted?
Such code for a long time, the logic looks very simple, but no one dare to use it.
What to do?
Use the generator mode to solve is better, each part as a generation steps, each performing a step is to add a widget configuration, eventually generating a complete computer quotation, and set up the parts, get discounts, counting quotation itself is orderly, these steps can be unified by Director of the generator model small cadre of operation.
Ok, look at the code!
Code “document”
Tips: Code + comments.
Self requirement: Comment coverage > 60%
1. First define a general configuration class of Computer quotation, that is, the product we want to generate: Computer
package builder
// Product: This is our target, computer with these configurations
// Computer can be understood as what product do we want to make
// Struct fields can be interpreted as the configuration of the product we want to do, corresponding to the above generated function N more input parameters
type Computer struct {
name string // Computer type such as MAC/Huawei
cpuModel CPU_MODEL / / CPU model
gpuModel GPU_MODEL / / the gpu models
memorySize MEM_SIZE // Memory size
diskSize DISK_SIZE // Disk size
discount float64 / / discount
price float64 // Overall quotation
}
Copy the code
2. Define a computer-generated step specification interface
package builder
// Generator interface: the generator interface of the product
Each subsequent abstract production generation object inherits the generator interface
type builder interface {
setCpuModel(CPU_MODEL) // Set the CPU model
setGpuModel(GPU_MODEL) // Set the GPU model
setMemorySize(MEM_SIZE) // Set the memory model
setDiskSize(DISK_SIZE) // Set the disk model
setDiscount() // Set the discount granularity. This discount granularity is built-in in the system and does not need to be set by the client. That is to say, this function is not customized for the front desk users when they inquire.
calculatePrice() // Calculate the quote
getComputer() *Computer // For the director
}
Copy the code
3. Start defining abstraction generators for each computer brand
Look at the Mac
package builder
import (
"time"
)
// Abstract product generator
// can be understood as a generator of a type of product in computer
// The abstract generator contains all the configuration of the product (Computer) and inherits all the steps of the builder common generator
type MacComputerBuilder struct {
c *Computer
}
// Strength a Mac quote
func NewMacComputerBuilder(a) builder {
return &MacComputerBuilder{
c: &Computer{name: "mac"}}},/ / return * Computer
func (mc *MacComputerBuilder) getComputer(a) *Computer {
return mc.c
}
// Set the CPU model
If the client is already configured, skip it
// This is because the director will execute the entire configuration in the final compilation to prevent the client from missing the configuration and go to the default configuration
func (mc *MacComputerBuilder) setCpuModel(m CPU_MODEL) {
// demo
ifmc.c.cpuModel ! ="" {
return
}
if price, ok := partsCpuPriceMap[m]; ok {
mc.c.cpuModel = m
mc.c.price += price
} else {
mc.c.cpuModel = MAC_CPU_I5 // This is the default CPU configuration for MAC computers
mc.c.price += partsCpuPriceMap[MAC_CPU_I5]
}
}
// Set the GPU model
If the client is already configured, skip it
// This is because the director will execute the entire configuration in the final compilation to prevent the client from missing the configuration and go to the default configuration
func (mc *MacComputerBuilder) setGpuModel(m GPU_MODEL) {
// demo
ifmc.c.gpuModel ! ="" {
return
}
if price, ok := partsGpuPriceMap[m]; ok {
mc.c.gpuModel = m
mc.c.price += price
} else {
mc.c.gpuModel = MAC_GPU_NVIDIA // This is the default GPU configuration for Macs
mc.c.price += partsGpuPriceMap[MAC_GPU_NVIDIA]
}
}
// Set the memory size
If the client is already configured, skip it
// This is because the director will execute the entire configuration in the final compilation to prevent the client from missing the configuration and go to the default configuration
func (mc *MacComputerBuilder) setMemorySize(s MEM_SIZE) {
// demo
ifmc.c.memorySize ! ="" {
return
}
if price, ok := partsMemPriceMap[s]; ok {
mc.c.memorySize = s
mc.c.price += price
} else {
mc.c.memorySize = MAC_MEM_8G // This is the default MAC memory configuration
mc.c.price += partsMemPriceMap[MAC_MEM_8G]
}
}
// Set the disk size
If the client is already configured, skip it
// This is because the director will execute the entire configuration in the final compilation to prevent the client from missing the configuration and go to the default configuration
func (mc *MacComputerBuilder) setDiskSize(s DISK_SIZE) {
// demo
ifmc.c.diskSize ! ="" {
return
}
if price, ok := partsDiskPriceMap[s]; ok {
mc.c.diskSize = s
mc.c.price += price
} else {
mc.c.diskSize = MAC_DISK_500G // This is the default disk configuration for the MAC
mc.c.price += partsDiskPriceMap[MAC_DISK_500G]
}
}
// Set the discount
// Different products have different strategies
// This operation is built-in and does not require external Settings
func (mc *MacComputerBuilder) setDiscount(a) {
/ / the 2021-06-24 00:17:33
// If the time is longer than that, the overall MAC is 50% off
// Otherwise, 20% off the whole package
if time.Now().Unix() > 1624465043 {
mc.c.discount = 0.5
} else {
mc.c.discount = 0.8}}// Count prices
// notice that this is where the time sequence is needed, and you need to setDiscount first to make the offer
// Therefore, it is necessary to unify the construction through the commander to ensure the execution sequence of each behavior
func (mc *MacComputerBuilder) calculatePrice(a) {
mc.c.price = (mc.c.price * mc.c.discount)
}
Copy the code
Looking at a Huawei.
package builder
import "C"
// Abstract product generator
// can be understood as a generator of a type of product in computer
// The abstract generator contains all the configuration of the product (Computer) and inherits all the steps of the builder common generator
type HuaweiComputerBuilder struct {
c *Computer
}
func NewHuaweiComputerBuilder(a) builder {
return &HuaweiComputerBuilder{
c: &Computer{name: "huawei"}}},func (hc *HuaweiComputerBuilder) getComputer(a) *Computer {
return hc.c
}
/** * the following configuration method is the same as the Mac logic, of course, can also customize the policy, but demo so, to ensure that the space, so I will not write */
// Set the CPU model
func (hc *HuaweiComputerBuilder) setCpuModel(m CPU_MODEL) {}
// Set the GPU model
func (hc *HuaweiComputerBuilder) setGpuModel(m GPU_MODEL) {}
// Set the memory size
func (hc *HuaweiComputerBuilder) setMemorySize(s MEM_SIZE) {}
// Set the disk size
func (hc *HuaweiComputerBuilder) setDiskSize(s DISK_SIZE) {}
// Set discounts, this is internal logic, does not need to be defined by external callers, and different products have different policies
func (hc *HuaweiComputerBuilder) setDiscount(a) {
// There is no discount for Huawei machines. That's what differentiates it from the MAC
hc.c.discount = 1
}
// Since Huawei does not discount, then direct export is good
func (hc *HuaweiComputerBuilder) calculatePrice(a){}Copy the code
See the difference? Two brand generatorsDifferent preferential strategies
.There are different ways of counting prices
But the unitySame generation steps
, so needdirector
To uniformly schedule execution
Look at the director
package builder
// The director is responsible for the overall build execution
He is in charge of calculating the quotations
type director struct {
builder builder
}
// instantiate a supervisor
func NewDirector(b builder) *director {
return &director{
builder: b,
}
}
// Manually reset the supervisor to facilitate multiple different product builds
func (d *director) resetBuilder(b builder) {
d.builder = b
}
// Execute the build, this is to strictly manage the compile steps and sequence
// Because the current demo is an example of calculating a quote rather than generating a computer configuration, the setXXX in front of it is executed on the client side
// However, it is possible that the foreground user did not select some configuration, so the supervisor needs to be unified
// 1. The bottom of each computer configuration
// 2. Select the discount granularity based on the current time
// 3. Calculate the quotation
func (d *director) buildComputer(a) *Computer {
// The first step is to cover every computer configuration
d.builder.setCpuModel(DIRECTOR_CHECK_PARAMS)
d.builder.setGpuModel(DIRECTOR_CHECK_PARAMS)
d.builder.setMemorySize(DIRECTOR_CHECK_PARAMS)
d.builder.setDiskSize(DIRECTOR_CHECK_PARAMS)
// The second step is to set the discount
d.builder.setDiscount()
// The third step is to calculate the quote
d.builder.calculatePrice()
// Return the product object
return d.builder.getComputer()
}
Copy the code
Is that pretty clear up here? Finally, let’s look at how the client calls the implementation:
package builder
import "fmt"
// The client asks for a quote
// The user selects a MAC from the foreground screen
// CPU i7
// GPU xxx
func getPrice(a) {
// Instantiate the abstract generator object, the MAC
mcb := NewMacComputerBuilder()
// Set the configuration I want to ask about
mcb.setCpuModel(MAC_CPU_I7)
mcb.setGpuModel(MAC_GPU_NVIDIA)
mcb.setMemorySize(MAC_MEM_16G)
// Use the default disk
//mcb.setDiskSize()
// Then instantiate a supervisor to prepare to generate the quote
d := NewDirector(mcb)
// Perform compilation to produce the final product
product := d.buildComputer()
// Ok, we can have a look at the final configuration and quotation of this product
fmt.Printf("current computer name: %s\n", product.name)
fmt.Printf("choose config cpuModel: %s\n", product.cpuModel)
fmt.Printf("choose config gpuModel: %s\n", product.gpuModel)
fmt.Printf("choose config memorySize: %s\n", product.memorySize)
fmt.Printf("choose config diskSize: %s\n", product.diskSize)
fmt.Printf("give you discount: %f\n", product.discount)
fmt.Printf("final offer: %f\n", product.price)
fmt.Printf("--------------- ask the next computer ---------------\n")
// Below we reproduce a huawei computer quote
hwcb := NewHuaweiComputerBuilder()
hwcb.setCpuModel(HW_CPU_I7)
hwcb.setGpuModel(HW_GPU_ATI)
hwcb.setMemorySize(HW_MEM_16G)
hwcb.setDiskSize(HW_DISK_1T)
d.resetBuilder(hwcb)
// Perform compilation to produce the final product
product2 := d.buildComputer()
// Ok, we can have a look at the final configuration and quotation of this product
fmt.Printf("current computer name: %s\n", product2.name)
fmt.Printf("choose config cpuModel: %s\n", product2.cpuModel)
fmt.Printf("choose config gpuModel: %s\n", product2.gpuModel)
fmt.Printf("choose config memorySize: %s\n", product2.memorySize)
fmt.Printf("choose config diskSize: %s\n", product2.diskSize)
fmt.Printf("give you discount: %f\n", product2.discount)
fmt.Printf("final offer: %f\n", product2.price)
}
Copy the code
Effect of online
=== RUN TestGetPrice current computer name: mac choose config cpuModel: maci7 choose config gpuModel: Mac-nvidia choose config memorySize: MAC-16G Choose config diskSize: MAC-500G Give you discount: 0.500000 final offer: 600.000000 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ask a computer -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the current computer name: huawei choose config cpuModel: hwi7 choose config gpuModel: hw-ATI choose config memorySize: hw-16g choose config diskSize: hw-1t give you discount: 1.000000 Final Offer: 2800.000000 -- PASS: TestGetPrice (0.00s) PASSCopy the code
As expected!
Demo source: github.com/xiaoxuz/des…
Advantages and disadvantages of generators
- advantages
- You can create objects step by step,
suspend
Create steps orRecursion operation
Create steps. - When you generate different forms of products, you can
Reuse the same manufacturing code
. * Single responsibility principle *
. You can separate complex construction code from the business logic of the product.
- You can create objects step by step,
- disadvantages
- Since this pattern requires multiple new classes, the code as a whole
complexity
It’s going to increase.
- Since this pattern requires multiple new classes, the code as a whole
thinking
Know our profession, not code farmer, is software engineer!
Call it a day
Thank you for reading!
【 click 】 Attention to look again, your attention is my motivation ~!