In general, a pattern has four essential elements:
pattern name, problem, solution, consequence
singleton is used when we need the object to be unique globally. the key point is, we set the object to be private for this package, then create a Get() function to use this object.
// Singleton definition.
type Singleton struct {
Name string
}
var (
instance *Singleton
)
// NewSingleton return singleton instance.
func NewSingleton() *Singleton {
if instance == nil {
instance = &Singleton{Name: "singleton"}
}
return instance
}
in the example above, when we r trying to get Singleton struct, we only get one struct globally.
Different struct realize the same interface. Also know as kit.
e.g. we r going to create a iSportsFactory interface, and both the Nike struct and the Adidas struct have the makeShoe() and makeShirt() method.
package main
import "fmt"
type iSportsFactory interface {
makeShoe() iShoe
makeShirt() iShirt
}
func getSportsFactory(brand string) (iSportsFactory, error) {
if brand == "adidas" {
return &adidas{}, nil
}
if brand == "nike" {
return &nike{}, nil
}
return nil, fmt.Errorf("Wrong brand type passed")
}
and then we can create adidas struct and nike struct, both struct already implemented two makeShoe()
and makeShirt()
functions.
package main
type adidas struct {
}
func (a *adidas) makeShoe() iShoe {
return &adidasShoe{
shoe: shoe{
logo: "adidas",
size: 14,
},
}
}
func (a *adidas) makeShirt() iShirt {
return &adidasShirt{
shirt: shirt{
logo: "adidas",
size: 14,
},
}
}
package main
type nike struct {
}
func (n *nike) makeShoe() iShoe {
return &nikeShoe{
shoe: shoe{
logo: "nike",
size: 14,
},
}
}
func (n *nike) makeShirt() iShirt {
return &nikeShirt{
shirt: shirt{
logo: "nike",
size: 14,
},
}
}
and for both the adidas struct and the nike struct can be used as the iSportsFactory interface.
in abstract factory pattern, functions in interface have fixed input and out params, sometimes we can abstract the type of input and output as interface.
So Factory Method means “use factory as a input or output method”
In factory method, we’ll encapsulate functions into interface.
package method
import "fmt"
type FoodKind int
const (
MeatKind FoodKind = iota
FruitKind
VegetableKind
NutKind
)
//Food 食物接口
type Food interface {
Eat()
}
//肉-食物接口的实现
type meat struct {
}
func (t *meat) Eat() {
fmt.Println("Eat meat")
}
//水果-食物接口的实现
type fruit struct {
}
func (t *fruit) Eat() {
fmt.Println("Eat fruit")
}
//蔬菜-食物接口的实现
type vegetable struct {
}
func (t *vegetable) Eat() {
fmt.Println("Eat vegetable")
}
//-------------干饭人的分割线-----------
//食物工厂
type Factory interface {
NewFood(k FoodKind) Food //这个函数是不是和简单工厂模式一模一样,而且分割线上面的代码也是一模一样
}
//肉厂
type MeatFactory struct {
}
func (t MeatFactory) NewFood(k FoodKind) Food {
return &meat{}
}
//水果厂
type FruitFactory struct {
}
func (t FruitFactory) NewFood(k FoodKind) Food {
return &fruit{}
}
//蔬菜厂
type VegetableFactory struct {
}
func (t VegetableFactory) NewFood(k FoodKind) Food {
return &vegetable{}
}
Usage
func main() {
fmt.Println("factory method pattern")
method.MeatFactory{}.NewFood(method.MeatKind).Eat() //去肉厂吃肉
method.FruitFactory{}.NewFood(method.FruitKind).Eat() //去水果厂吃水果
method.VegetableFactory{}.NewFood(method.VegetableKind).Eat()
method.NutFactory{}.NewFood(method.NutKind).Eat()
}
we use prototype when we r trying to create duplicated objects.
package prototype
// Cloneable接口,动物必须实现这个接口
type Cloneable interface {
Clone() Cloneable
}
// 克隆实验室
type CloneLab struct {
animals map[string]Cloneable
}
func NewPrototypeManager() *CloneLab {
return &CloneLab{animals:make(map[string]Cloneable)}
}
// 获取克隆
func (p *CloneLab) Get(name string) Cloneable {
return p.animals[name]
}
// set动物当前属性
func (p *CloneLab) Set(name string,prototype Cloneable) {
p.animals[name] = prototype
}
prototype_test.go:
package prototype
import (
"testing"
)
var lab *Cloneable
// 羊
type Sheep struct {
name string
weight int
}
func (s *Sheep) Clone() Cloneable {
tc := *s
return &tc
}
// 牛
type Cow struct {
name string
gender bool
}
func (c *Cow) Clone() Cloneable {
newCow := &Cow{
name: c.name,
gender: c.gender,
}
return newCow
}
func TestClone(t *testing.T) {
sheep1 := &Sheep{
name: "sheep",
weight: 10,
}
sheep2 := sheep1.Clone()
// 这里地址肯定不同,因为是一个新的实例
if sheep1 == sheep2 {
t.Fail()
}
}
func TestCloneFromManager(t *testing.T) {
lab := NewCloneLab()
lab.Set("cow", &Cow{name: "i am cow", gender: true})
c := lab.Get("cow").Clone()
cw := c.(*Cow)
if cw.name != "i am cow" {
t.Fatal("error")
}
}
in the code above, we created an interface, and the function in this interface will return this interface itself. And when we create a struct to inherit this interface, we can clone new object via the clone function.
Builder is used when the desired object requires complex steps to complete. Builder allows us to separate the constructions of a complex object.
The builder start with a struct which has an interface attribution. The methods in this interface will return an initiated object.
With builder, we can initiate different objects with the same construction code.
package builder
type PizzaProcess interface {
PizzaDough() PizzaProcess
PizzaSauce() PizzaProcess
PizzaTopping() PizzaProcess
GetPizza() PizzaProduct
}
type PizzaProgress struct {
builder PizzaProcess
}
func (f *PizzaProgress) Construct() {
f.builder.PizzaSauce().PizzaTopping().PizzaDough()
}
func (f *PizzaProgress) SetPizza(b PizzaProcess) {
f.builder = b
}
type PizzaProduct struct {
Dough string
Sauce string
Topping string
}
type VegPizza struct {
v PizzaProduct
}
func (veg *VegPizza) PizzaDough() PizzaProcess {
veg.v.Dough = "Small"
return veg
}
func (veg *VegPizza) PizzaSauce() PizzaProcess {
veg.v.Sauce = "Bechamel"
return veg
}
func (veg *VegPizza) PizzaTopping() PizzaProcess {
veg.v.Topping = "Mushrooms"
return veg
}
func (veg *VegPizza) GetPizza() PizzaProduct {
return veg.v
}
type NonVegPizza struct {
n PizzaProduct
}
func (non *NonVegPizza) PizzaDough() PizzaProcess {
non.n.Dough = "Large"
return non
}
func (non *NonVegPizza) PizzaSauce() PizzaProcess {
non.n.Sauce = "Pesto"
return non
}
func (non *NonVegPizza) PizzaTopping() PizzaProcess {
non.n.Topping = "Pepperoni"
return non
}
func (non *NonVegPizza) GetPizza() PizzaProduct {
return non.n
}
Adapter pattern works as a bridge between two incompatible interfaces.
we have a charger(client) with lighting port, and can charge MAC, but since windows need to use USB, so we cannot charge windows with this charger(client) , but if we use adapter, then we can use this charger to charge windows device.
package main
import "fmt"
type client struct {
}
func (c *client) excuteProgram(s system) {
s.chargeWithLighting()
}
type system interface {
chargeWithLighting()
}
type mac struct {
}
func (m *mac) chargeWithLighting() {
fmt.Println("MAC: I'm charging")
}
type windows struct{}
func (w *windows) chargeWithUSB() {
fmt.Println("windows: I'm charging")
}
type windowsAdapter struct {
windowMachine *windows
}
func (w *windowsAdapter) chargeWithLighting() {
fmt.Println("Adapter is working")
w.windowMachine.chargeWithUSB()
}
func main() {
client := &client{}
mac := &mac{}
client.excuteProgram(mac)
windowsMachine := &windows{}
windowsMachineAdapter := &windowsAdapter{
windowMachine: windowsMachine,
}
client.excuteProgram(windowsMachineAdapter)
}
to separate the abstraction and implementation. for computer systems we have mac and windows, for printer brand, we have epson and hp. if we want to execute the behaviour of “print”, then actually we don’t need to create 2*2 methods. We can use bridge mode to separate the computer and printer into interface.
package main
import "fmt"
type computer interface {
print()
setPrinter(printer)
}
type printer interface {
printFile()
}
type mac struct {
printer printer
}
func (m *mac) setPrinter(p printer) {
m.printer = p
}
func (m *mac) print() {
m.printer.printFile()
}
type windows struct {
printer printer
}
func (w *windows) setPrinter(p printer) {
w.printer = p
}
func (w *windows) print() {
w.printer.printFile()
}
type epson struct {
}
func (p *epson) printFile() {
fmt.Println("EPSON: I'm printing")
}
type hp struct {
}
func (p *hp) printFile() {
fmt.Println("HP: I'm printing")
}
func main() {
hpPrinter := &hp{}
epsonPrinter := &epson{}
macComputer := &mac{}
macComputer.setPrinter(hpPrinter)
macComputer.print()
fmt.Println()
macComputer.setPrinter(epsonPrinter)
macComputer.print()
fmt.Println()
winComputer := &windows{}
winComputer.setPrinter(hpPrinter)
winComputer.print()
fmt.Println()
winComputer.setPrinter(epsonPrinter)
winComputer.print()
fmt.Println()
}
Composite allows composing objects into a tree-like structure and work with the it as if it was a singular object.
like suppose we want to search a keyword in a folder, this
package main
import "fmt"
type component interface {
search(string)
}
type file struct {
name string
}
func (f *file) search(keyword string) {
fmt.Printf("file %s: searching for %s\n", f.name, keyword)
}
func (f *file) getName() string {
return f.name
}
type folder struct {
components []component
name string
}
func (f *folder) search(keyword string) {
fmt.Printf("folder %s: searching for %s\n", f.name, keyword)
for _, component := range f.components {
component.search(keyword)
}
}
func (f *folder) add(c component) {
f.components = append(f.components, c)
}
func main() {
file1 := &file{name: "File1"}
file2 := &file{name: "File2"}
file3 := &file{name: "File3"}
folder1 := &folder{
name: "Folder1",
}
folder1.add(file1)
folder2 := &folder{
name: "Folder2",
}
folder2.add(file2)
folder2.add(file3)
folder2.add(folder1)
folder2.search("rose")
}
there is a basic struct with its functions, use an interface to represent this struct, then use this interface as an attribution inside an advanced struct. So this advanced struct can implement these functions based on the basic struct.
for example, we have a pizza interface, this interface has a getPrice() function. then we can set a topping decorator struct, which contains this pizza interface as an attribute, then we can add price based on this interface function.
package main
import "fmt"
type pizza interface {
getPrice() int
}
type basicPizza struct {
}
func (p *basicPizza) getPrice() int {
return 15
}1
type toppingMeat struct {
pizza pizza
}
func (m *toppingMeat) getPrice() int {
return m.pizza.getPrice() + 7
}
func main() {
myPizza := &basicPizza{}
meatPizza := &toppingMeat{
pizza: myPizza,
}
fmt.Println(meatPizza.getPrice())
}
Facade shields complicated systems by providing a simplified interface of classes. so the client can ignore the complicated internal implementation.
Basically it will hide non-Facade functions as lower case functions. And will provide Facade functions as UpperCase functions.
Flyweight is a software design pattern. A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory.
like if we want to store any objects in our memory, we can consider first whether we can store the pointers which represents these objects, rather than store these objects directly.
The proxy object has the same interface as a service, which makes it interchangeable with a real object when passed to a client. like nginx or some middleware.
The key point is the principal and the proxy will implement the same interface; and the proxy struct will include the principal struct as an attribution.
package main
type server interface {
handleRequest(string, string) (int, string)
}
type nginx struct {
application *application
maxAllowedRequest int
rateLimiter map[string]int
}
func newNginxServer() *nginx {
return &nginx{
application: &application{},
maxAllowedRequest: 2,
rateLimiter: make(map[string]int),
}
}
func (n *nginx) handleRequest(url, method string) (int, string) {
allowed := n.checkRateLimiting(url)
if !allowed {
return 403, "Not Allowed"
}
return n.application.handleRequest(url, method)
}
func (n *nginx) checkRateLimiting(url string) bool {
if n.rateLimiter[url] == 0 {
n.rateLimiter[url] = 1
}
if n.rateLimiter[url] > n.maxAllowedRequest {
return false
}
n.rateLimiter[url] = n.rateLimiter[url] + 1
return true
}
type application struct {
}
func (a *application) handleRequest(url, method string) (int, string) {
if url == "/app/status" && method == "GET" {
return 200, "Ok"
}
if url == "/create/user" && method == "POST" {
return 201, "User Created"
}
return 404, "Not Ok"
}
allows passing request along the chain of potential handlers until one of them handles request.
package main
type department interface {
execute(*patient)
setNext(department)
}
type reception struct {
next department
}
func (r *reception) execute(p *patient) {
if p.registrationDone {
fmt.Println("Patient registration already done")
r.next.execute(p)
return
}
fmt.Println("Reception registering patient")
p.registrationDone = true
r.next.execute(p)
}
func (r *reception) setNext(next department) {
r.next = next
}
type doctor struct {
next department
}
type patient struct {
name string
registrationDone bool
doctorCheckUpDone bool
medicineDone bool
paymentDone bool
}
func (d *doctor) execute(p *patient) {
if p.doctorCheckUpDone {
fmt.Println("Doctor checkup already done")
d.next.execute(p)
return
}
fmt.Println("Doctor checking patient")
p.doctorCheckUpDone = true
d.next.execute(p)
}
func (d *doctor) setNext(next department) {
d.next = next
}
type medical struct {
next department
}
func (m *medical) execute(p *patient) {
if p.medicineDone {
fmt.Println("Medicine already given to patient")
m.next.execute(p)
return
}
fmt.Println("Medical giving medicine to patient")
p.medicineDone = true
m.next.execute(p)
}
func (m *medical) setNext(next department) {
m.next = next
}
type cashier struct {
next department
}
func (c *cashier) execute(p *patient) {
if p.paymentDone {
fmt.Println("Payment Done")
}
fmt.Println("Cashier getting money from patient patient")
}
func (c *cashier) setNext(next department) {
c.next = next
}
func main() {
cashier := &cashier{}
//Set next for medical department
medical := &medical{}
medical.setNext(cashier)
//Set next for doctor department
doctor := &doctor{}
doctor.setNext(medical)
//Set next for reception department
reception := &reception{}
reception.setNext(doctor)
patient := &patient{name: "abc"}
//Patient visiting
reception.execute(patient)
}
so for the example above, all department is an implementation of interface department, and by checking the patient status, all the department can determine whether to execute current process under current department.
can use when need to do some duplicated work. a good illustration here:https://www.sohamkamani.com/golang/command-pattern/
func main() {
// initialize a new resaurant
r := NewResteraunt()
// create the list of tasks to be executed
tasks := []Command{
r.MakePizza(2),
r.MakeSalad(1),
r.MakePizza(3),
r.CleanDishes(),
r.MakePizza(4),
r.CleanDishes(),
}
// create the cooks that will execute the tasks
cooks := []*Cook{
&Cook{},
&Cook{},
}
// Assign tasks to cooks alternating between the existing
// cooks.
for i, task := range tasks {
// Using the modulus of the current task index, we can
// alternate between different cooks
cook := cooks[i%len(cooks)]
cook.Commands = append(cook.Commands, task)
}
// Now that all the cooks have their commands, we can call
// the `executeCommands` method that will have each cook
// execute their respective commands
for i, c := range cooks {
fmt.Println("cook", i, ":")
c.executeCommands()
}
}
reference: https://blog.csdn.net/qibin0506/article/details/50812611
we will pass different expression(grammer) into a interpreter, and the interpreter will generate different result according to different expression.
package main
import "fmt"
// Expression
type Expression interface {
Interpreter(a, b int) int
}
// AddExpression Add
type AddExpression struct {
}
func (t AddExpression) Interpreter(a, b int) int {
return a + b
}
// SubExpression Sub
type SubExpression struct {
}
func (t SubExpression) Interpreter(a, b int) int {
return a - b
}
// MulExpression multiply
type MulExpression struct {
}
func (t MulExpression) Interpreter(a, b int) int {
return a * b
}
// PowerExpression power
type PowerExpression struct {
}
func (t PowerExpression) Interpreter(a, b int) int {
result := 1
for i := 0; i < b; i++ {
result *= a
}
return result
}
type CalcParser struct {
expr1 Expression
expr2 Expression
}
// according to different interpreter,to process 3 parameters and generate a result
func (t CalcParser) interperter(a, b, c int) int {
return t.expr1.Interpreter(a, t.expr2.Interpreter(b, c))
}
func main() {
add := AddExpression{}
power := PowerExpression{}
p := CalcParser{expr1: add, expr2: power}
fmt.Println(p.interperter(1, 2, 3)) // interpreted as 1+ 2^3, the result is 9
p.expr2 = MulExpression{}
fmt.Println(p.interperter(1, 2, 3)) // interpreted as 1+ 2*3 the result is 7
}
package main
import "fmt"
type Ints []int
func (i Ints) Iterator() *Iterator {
return &Iterator{
data: i,
index: 0,
}
}
type Iterator struct {
data Ints
index int
}
func (i *Iterator) HasNext() bool {
return i.index < len(i.data)
}
func (i *Iterator) Next() (v int) {
v = i.data[i.index]
i.index++
return v
}
func main() {
ints := Ints{1, 2, 3}
for it := ints.Iterator(); it.HasNext(); {
fmt.Println(it.Next())
}
}
a system may contain a lot of modules, each modules might import other modules. Mediator is to reduce the coupling between these modules. for example, in a chatting room, when a client try to chat with other members, they can send the msg to the chat room first, then the chat room will redirect this msg to other clients.
//ChatRoom mediacotr
type ChatRoom struct{}
var chatRoom = NewChatRoom()
//NewChatRoom Initialize
func NewChatRoom() *ChatRoom {
return &ChatRoom{}
}
//ShowMessage
func (cr *ChatRoom) ShowMessage(user *User, msg string) {
fmt.Printf("%s: [ %s ]: %s \n",
time.Now().Format("2006-01-02 15:04:05"),
user.GetName(),
msg)
}
//User
type User struct {
Name string
}
//NewUser 实例化用户类
func NewUser(name string) *User {
return &User{
Name: name,
}
}
//SendMessage 用户类使用中介者发送消息
func (u *User) SendMessage(msg string) {
chatRoom.ShowMessage(u, msg)
}
//GetName 获取用于昵称
func (u *User) GetName() string {
return u.Name
}
Memento will keep a statu of an object, and can help us roll back.
//Memento 备忘录类
type Memento struct {
state string
}
//NewMemento 实例化备忘录类
func NewMemento(st string) *Memento {
return &Memento{
state: st,
}
}
//GetState 获取备忘录类的状态
func (m *Memento) GetState() string {
return m.state
}
Used in the Publisher-Subscriber mode
used in infinite state machine, use a switch-case logic to process the object status
in cache scenario, u can choose different cache eviction policy, with strategy pattern, u can change the strategy pattern without restart the program.
https://golangbyexample.com/strategy-design-pattern-golang/
Template lets you define a template or algorithm for a particular operation. a bit like abstract factory in implementation.
Visitor lets you add behaviour to a struct without actually modifying the struct.
Suppose there is 1 interface and several structs which implemented this interface:
type InternalInterface interface {
InternalFun()
}
type InternalStruct1 struct {
}
func (s1 *InternalStruct1) InternalFun() {}
type InternalStruct2 struct {
}
func (s2 *InternalStruct2) InternalFun() {}
we don’t want to change the internal struct1 and the internal struct2(or maybe they were encapsulated), but we want to extend with some functions. then we can add an accept function to internal interface, and create a visitor interface to implement these functions.
type InternalInterface interface{
InternalFun()
accept(visitor)
}
type visitor interface {
VisitInternalStruct1()
VisitInternalStruct2()
}
after this, we can create struct which implement this visitor interface. So by implementing the accept function, outer struct can visit the interval struct.
a good example here:
https://medium.com/@felipedutratine/visitor-design-pattern-in-golang-3c142a12945a