Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize 23 Design patterns in Go language

2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)05/31 Report--

The knowledge of this article "how to achieve 23 design patterns in Go language" is not understood by most people, so the editor summarizes the following content, detailed content, clear steps, and has a certain reference value. I hope you can get something after reading this article. Let's take a look at this "how to achieve 23 design patterns in Go language" article.

Create a model

Creative pattern is a design pattern that deals with the creation of objects, trying to create objects in an appropriate way according to the actual situation, so as to increase the flexibility and reusability of existing code.

Factory method pattern Factory Method problem

Assuming that our business needs a payment channel, we have developed a Pay method that can be used for payment. Look at the following example:

Type Pay interface {Pay () string} type PayReq struct {OrderId string / / order number} func (p * PayReq) Pay () string {/ / todo fmt.Println (p.OrderId) return "payment successful"}

As above, we defined the interface Pay and implemented its method Pay ().

If the business requirements change, we need to provide a variety of payment methods, one is called APay and the other is called BPay. The parameters required for these two payment methods are different. APay only needs the order number OrderId,BPay and requires the order number OrderId and Uid. How to modify it at this time?

It is easy to think of modifications based on the original code, such as:

Type Pay interface {APay () string BPay () string} type PayReq struct {OrderId string / / order No. Uid int64} func (p * PayReq) APay () string {/ / todo fmt.Println (p.OrderId) return "APay payment"} func (p * PayReq) BPay () string {/ / todo fmt.Println (p.OrderId) fmt.Println (p.Uid) return "BPay payment"}

We implemented the APay () and BPay () methods for the Pay interface. Although the business requirements are temporarily implemented, it makes the structure PayReq redundant, and APay () does not require the Uid parameter. If you add CPay, DPay, and EPay later, it is conceivable that the code will become more and more difficult to maintain.

With subsequent business iterations, complex code will have to be written.

Solve

Let's imagine a factory class, which needs to produce appliances such as wires and switches, and we can provide a production method for the factory class. When the wire machine invokes the production method, it produces the wire, and when the switch machine invokes the production method, it produces the switch.

To apply to our payment business, we no longer provide APay method or BPay method for the interface, but only provide a Pay method, and devolve the difference between A payment method and B payment method to subcategories.

Take a look at the example:

Package factorymethodimport "fmt" type Pay interface {Pay (string) int} type PayReq struct {OrderId string} type APayReq struct {PayReq} func (p * APayReq) Pay () string {/ / todo fmt.Println (p.OrderId) return "APay payment success" type BPayReq struct {PayReq Uid int64} func (p * BPayReq) Pay () string {/ / todo fmt.Println (p.OrderId) fmt.Println (p.Uid) return "BPay payment success"}

We write the Pay () method with the structural weight of APay and BPay. If you need to add a new payment method, you only need to rewrite the new Pay () method.

The advantage of the factory approach is that it avoids the tight coupling between the creator and the specific product, making the code easier to maintain.

Test the code:

Package factorymethodimport ("testing") func TestPay (t * testing.T) {aPay: = APayReq {} if aPay.Pay ()! = "APay payment success" {t.Fatal ("aPay error")} bPay: = BPayReq {} if bPay.Pay ()! = "BPay payment success" {t.Fatal ("bPay error")}} abstract factory pattern Abstract Factory problem

The abstract factory pattern is based on the factory method pattern. The difference between the two is that the factory method pattern is to create a product, while the abstract factory pattern is to create a product. Both of them belong to factory mode and are similar in design.

Suppose you have a storage factory that provides both redis and mysql ways to store data. If we use the factory method pattern, we need a storage factory and provide the SaveRedis method and the SaveMysql method.

If the business still needs to be divided into two carriers: storage prose and ancient poetry, both of them can be stored by redis and mysql. To use the abstract factory pattern, we need a storage factory as the parent factory, the prose factory and the ancient poetry factory as the child factory, and provide SaveRedis and SaveMysql methods.

Solve

Taking the storage factory business above as an example, design the code with the idea of an abstract factory pattern, like this:

Package abstractfactoryimport "fmt" / / SaveArticle Abstract pattern Factory Interface type SaveArticle interface {CreateProse () Prose CreateAncientPoetry () AncientPoetry} type SaveRedis struct {} func (* SaveRedis) CreateProse () Prose {return & RedisProse {} func (* SaveRedis) CreateAncientPoetry () AncientPoetry {return & RedisProse {} type SaveMysql struct {} func (* SaveMysql) CreateProse () Prose {return & MysqlProse {} func (* SaveMysql) CreateAncientPoetry () AncientPoetry {return & MysqlProse {} / / Prose Prose type Prose interface {SaveProse () ) / / AncientPoetry Ancient poem type AncientPoetry interface {SaveAncientPoetry ()} type RedisProse struct {} func (* RedisProse) SaveProse () {fmt.Println ("Redis SaveProse")} func (* RedisProse) SaveAncientPoetry () {fmt.Println ("Redis SaveAncientPoetry")} type MysqlProse struct {} func (* MysqlProse) SaveProse () {fmt.Println ("Mysql SaveProse")} func (* MysqlProse) SaveAncientPoetry () {fmt.Println ("Mysql SaveAncientPoetry")}

We define the storage factory, the SaveArticle interface, and implement the CreateProse method and the CreateAncientPoetry method, which are used to create prose factories and ancient poetry factories, respectively.

Then we implement SaveProse method and SaveAncientPoetry method for prose factory and ancient poetry factory respectively, and rewrite two storage methods with Redis structure and Mysql structure respectively.

Test the code:

Package abstractfactoryfunc Save (saveArticle SaveArticle) {saveArticle.CreateProse (). SaveProse () saveArticle.CreateAncientPoetry (). SaveAncientPoetry ()} func ExampleSaveRedis () {var factory SaveArticle factory = & SaveRedis {} Save (factory) / Output: / / Redis SaveProse / / Redis SaveAncientPoetry} func ExampleSaveMysql () {var factory SaveArticle factory = & SaveMysql {} Save (factory) / / Output: / / Mysql SaveProse / / Mysql SaveAncientPoetry} Builder Mode Builder problem

Assuming that the business needs to create a series of complex objects step by step, and the code to implement these steps is very complex, we can put the code into a constructor with many parameters, but this constructor will look very messy and difficult to maintain.

Suppose the business needs to build a house object, you need to build foundations, walls, roofs, gardens, and furniture. We need a lot of steps, and there is a connection between them, and even if we extract each step from a large constructor to other small functions, the hierarchy of the entire program still looks complex.

How to solve the problem? A complex constructor with many steps like this can be designed using the builder pattern.

The use of the builder pattern is to be able to create complex objects step by step.

Solve

In builder mode, we need to clearly define the code for each step, and then operate these steps in a constructor, and we need a supervisor class to manage the steps. In this way, we only need to pass the required parameters to a constructor, which then passes the parameters to the corresponding supervisor class, and finally the supervisor class completes all the subsequent construction tasks.

Look at the following code:

Package builderimport "fmt" / / Builder interface type Builder interface {Part1 () Part2 () Part3 ()} / / Management class type Director struct {builder Builder} / / Constructor func NewDirector (builder Builder) * Director {return & Director {builder: builder } / / build func (d * Director) Construct () {d.builder.Part1 () d.builder.Part2 () d.builder.Part3 ()} type Builder struct {} func (b * Builder) Part1 () {fmt.Println ("part1")} func (b * Builder) Part2 () {fmt.Println ("part2")} func (b * Builder) Part3 () {fmt.Println ("part3")}

As above, we implement the three steps of part1, part2, and part3. Only need to execute the constructor, and the corresponding management class can run the construction method Construct to complete the execution of the three steps.

Test the code:

Package builderfunc ExampleBuilder () {builder: = & Builder {} director: = NewDirector (builder) director.Construct () / / Output: / / part1 / / part2 / / part3} prototype pattern Prototype problem

If you want to generate an object that is exactly the same as another object, how do you do it?

If you iterate through all the members of an object and copy them into the new object in turn, it will be a bit cumbersome, and some objects may have private member variables missing.

The prototype pattern delegates the cloning process to the actual object being cloned, and the cloned object is called the prototype.

Solve

If you need to clone a new object that is completely independent of its prototype, you can use the prototype pattern.

The implementation of the prototype pattern is very simple, take a look at the following code:

Package prototypeimport "testing" var manager * PrototypeManagertype Type1 struct {name string} func (t * Type1) Clone () * Type1 {tc: = * t return & tc} func TestClone (t * testing.T) {T1: = & Type1 {name: "type1",} T2: = t1.Clone () if T1 = = T2 {t.Fatal ("error! get clone not working")}}

We rely on a Clone method to clone the prototype Type1.

The use of the prototype pattern is that we can clone objects without coupling with the dependencies of the prototype objects.

Singleton mode Singleton problem

A global variable that stores important objects often means "unsafe" because you cannot guarantee that the value of this global variable will not be overwritten somewhere in the project.

Changes to the data often lead to unexpected results and hard-to-find bug. I update the data in one place, but I don't realize that another part of the software is expecting completely different data, so a function fails, and it can be very difficult to find the cause of the failure.

A better solution is to encapsulate such "variable data" and write a query method specifically to get these values.

The singleton pattern goes a step further: in addition to providing a global access method for "mutable data", it also ensures that only the same instance is obtained. That is, if you plan to create an object with a constructor, the singleton pattern will ensure that what you get is not a new object, but a previously created object, and each time it returns only the same object, that is, the singleton. This protects the object instance from tampering.

Solve

The singleton pattern requires a global constructor that returns a private object that always returns the same object whenever called.

Look at the following code:

Package singletonimport ("sync") / / Singleton instance type singleton struct {Value int} type Singleton interface {getValue () int} func (s singleton) getValue () int {return s.Value} var (instance * singleton once sync.Once) / / Construction method, used to obtain singleton pattern object func GetInstance (v int) Singleton {once.Do (func () {instance = & singleton {Value: v}) return instance}

The singleton instance singleton is saved as a private variable to ensure that it is not referenced by the functions of other packages.

A singleton instance can be obtained by using the constructor GetInstance. The once method of the sync package is used in the function to ensure that the instance will only be initialized once on the first call, and then calling the constructor will only return the same instance.

Test the code:

Func TestSingleton (t * testing.T) {ins1: = GetInstance2 (1) ins2: = GetInstance2 (2) if ins1! = ins2 {t.Fatal ("instance is not equal")}}

If you need stricter control over global variables, which is really necessary, use singleton mode.

Structural model

Structural patterns assemble some objects and classes into larger structures while keeping the structure flexible and efficient.

Adapter pattern Adapter problem

To put it bluntly, the adapter pattern is compatible.

Suppose we provide the An object at the beginning, and later, as the business iterates, we need to derive different requirements from the An object. If many functions have already called the An object online, it will be troublesome to modify the An object at this time, because compatibility issues need to be considered. To make matters worse, you may not have the source code of the library and cannot modify it.

At this point, you can use an adapter, which is like an interface converter, and the caller only needs to call the adapter interface without paying attention to the implementation behind it, encapsulating the complex process by the adapter interface.

Solve

Suppose there are two interfaces, one converts centimeters to meters, and the other converts meters to centimeters. We provide an adapter interface so that the caller does not have to worry about which interface to call and is directly compatible with the adapter.

Look at the following code:

Package adapter// provides an interface for obtaining meters and an interface for obtaining centimeters: type Cm interface {getLength (float64) float64} type M interface {getLength (float64) float64} func NewM () M {return & getLengthM {} type getLengthM struct {} func (* getLengthM) getLength (cm float64) float64 {return cm / 10} func NewCm () Cm {return & getLengthCm {} type getLengthCm struct {} func (a * getLengthCm) getLength (m float64) float64 {return m * 10} / / Adapter type LengthAdapter interface {getLength (string Float64) float64} func NewLengthAdapter () LengthAdapter {return & getLengthAdapter {} type getLengthAdapter struct {} func (* getLengthAdapter) getLength (isType string, into float64) float64 {if isType = = "m" {return NewM (). GetLength (into)} return NewCm (). GetLength (into)}

The above two interfaces Cm and M are implemented and compatible with the adapter LengthAdapter.

Test the code:

Package adapterimport "testing" func TestAdapter (t * testing.T) {into: = 10.5getLengthAdapter: = NewLengthAdapter () .getLength ("m", into) getLengthM: = NewM () .getLength (into) if getLengthAdapter! = getLengthM {t.Fatalf ("getLengthAdapter:% f, getLengthM:% f", getLengthAdapter, getLengthM)}} bridging mode Bridge problem

Assuming that the business needs two channels to send information, sms and email, we can implement sms and email interfaces respectively.

After that, with the business iteration, there are new requirements, which need to provide two system delivery methods, systemA and systemB, and both of these two system delivery modes should support sms and email channels.

At this point, at least four methods need to be provided: systemA to sms,systemA to email,systemB to sms,systemB to email.

If one more channel and one system transmission mode are added respectively, nine methods need to be provided. This leads to an exponential increase in the complexity of the code.

Solve

In fact, we were looking at the problem with the idea of inheritance, while the bridging mode hopes to transform the inheritance relationship into an association relationship, so that the two classes exist independently.

Tell me more about it:

The bridging pattern needs to distinguish between abstraction and implementation

Bridging mode needs to distinguish between "channel" and "system transmission mode".

Finally, the abstract interface of "channel" is called in the class of "system sending mode" to change them from inheritance relationship to association relationship.

Summarize the idea of the bridging pattern in one sentence: "decouple abstraction from implementation, and change different types of inheritance relationships to association relationships."

Look at the following code:

Package bridgeimport "fmt" / / two ways to send messages type SendMessage interface {send (text, to string)} type sms struct {} func NewSms () SendMessage {return & sms {} func (* sms) send (text, to string) {fmt.Println (fmt.Sprintf ("send% s to% s sms", text, to))} type email struct {} func NewEmail () SendMessage {return & email {} func (* email) send (text) To string) {fmt.Println (fmt.Sprintf ("send% s to% s email", text, to))} / / two transmission systems type systemA struct {method SendMessage} func NewSystemA (method SendMessage) * systemA {return & systemA {method: method,}} func (m * systemA) SendMessage (text, to string) {m.method.send (fmt.Sprintf ("[SystemA]% s", text) To)} type systemB struct {method SendMessage} func NewSystemB (method SendMessage) * systemB {return & systemB {method: method,}} func (m * systemB) SendMessage (text, to string) {m.method.send (fmt.Sprintf ("[SystemB]% s", text), to)}

You can see that we first defined two implementations of sms and email, as well as the interface SendMessage. Then we implemented systemA and systemB and called the abstract interface SendMessage.

Test the code:

Package bridgefunc ExampleSystemA () {NewSystemA (NewSms ()) .SendMessage ("hi", "baby") NewSystemA (NewEmail ()) .SendMessage ("hi", "baby") / / Output: / / send [SystemA] hi to baby sms / / send [SystemA] hi to baby email} func ExampleSystemB () {NewSystemB (NewSms ()) .SendMessage ("hi", "baby") NewSystemB (NewEmail ()) .SendMessage ("hi") "baby") / / Output: / / send [System B] hi to baby sms / / send [System B] hi to baby email}

If you want to split or reassemble a complex class with multiple functions, you can use bridging mode.

Object Tree problem of object tree mode

In a project, if we need to use a tree structure, we can use the object tree pattern. In other words, if the core model of the project cannot be represented in a tree structure, there is no need to use the object tree pattern.

The use of the object tree pattern is that complex tree structures can be used more easily by using polymorphisms and recursive mechanisms.

Solve

Look at the following code:

Package objecttreeimport "fmt" type Component interface {Parent () Component SetParent (Component) Name () string SetName (string) AddChild (Component) Search (string)} const (LeafNode = iota CompositeNode) func NewComponent (kind int Name string) Component {var c Component switch kind {case LeafNode: C = NewLeaf () case CompositeNode: C = NewComposite ()} c.SetName (name) return c} type component struct {parent Component name string} func (c * component) Parent () Component {return c.parent} func (c * component) SetParent (parent Component) {c.parent = parent} func (c * component) Name () string {return c.name} func (c * component) SetName ( Name string) {c.name = name} func (c * component) AddChild (Component) {} type Leaf struct {component} func NewLeaf () * Leaf {return & Leaf {} func (c * Leaf) Search (pre string) {fmt.Printf ("leaf% s% s\ n" Pre, c.Name ()} type Composite struct {component childs [] Component} func NewComposite () * Composite {return & Composite {childs: make ([] Component, 0),}} func (c * Composite) AddChild (child Component) {child.SetParent (c) c.childs = append (c.childs, child)} func (c * Composite) Search (pre string) {fmt.Printf ("% slots% s\ n", pre, c.Name ()) pre + = "for _ Comp: = range c.childs {comp.Search (pre)}}

The whole tree structure is printed out recursively in the Search method.

Test the code:

Package objecttreefunc ExampleComposite () {root: = NewComponent (CompositeNode, "root") C1: = NewComponent (CompositeNode, "C1") c2: = NewComponent (CompositeNode, "c2") c3: = NewComponent (CompositeNode, "c3") L1: = NewComponent (LeafNode, "L1") L2: = NewComponent (LeafNode, "L2") L3: = NewComponent (LeafNode "L3") root.AddChild (C1) root.AddChild (c2) c1.AddChild (c3) c1.AddChild (L1) c2.AddChild (L2) c2.AddChild (L3) root.Search ("") / Output: / / + root / / + c3 / / leaf-L1 / / + c2 / / leaf-12 / / leaf-L3} Decoration pattern Decorator problem

Sometimes we need to extend one class to another, for example, a pizza, you can add tomato pizza and cheese pizza to pizza. At this point, you can use the decoration mode, which simply encapsulates the object into another object to bind the new behavior to the original object.

If you want to use the object without modifying the code, and you want to add additional behavior to the object, you can consider using decoration mode.

Solve

Use the pizza category above as an example. Look at the following code:

Package decoratortype pizza interface {getPrice () int} type base struct {} func (p * base) getPrice () int {return 15} type tomatoTopping struct {pizza pizza} func (c * tomatoTopping) getPrice () int {pizzaPrice: = c.pizza.getPrice () return pizzaPrice + 10} type cheeseTopping struct {pizza pizza} func (c * cheeseTopping) getPrice () int {pizzaPrice: = c.pizza.getPrice () return pizzaPrice + 20}

First, we define the pizza interface, create the base class, and implement the method getPrice. Then we implement the tomatoTopping and cheeseTopping classes with the idea of decorative mode, which encapsulate the getPrice method of the pizza interface.

Test the code:

Package decoratorimport "fmt" func ExampleDecorator () {pizza: = & base {} / / Add cheese topping pizzaWithCheese: = & cheeseTopping {pizza: pizza,} / Add tomato topping pizzaWithCheeseAndTomato: = & tomatoTopping {pizza: pizzaWithCheese,} fmt.Printf ("price is% d\ n", pizzaWithCheeseAndTomato.getPrice ()) / / Output: / / price is 45} appearance mode Facade problem

If you need to initialize a large number of complex libraries or frameworks, you need to manage their dependencies and execute them in the correct order. At this point, these dependencies can be handled uniformly with an appearance class to integrate them.

Solve

The appearance pattern is very similar to the builder pattern. The difference between the two is that the appearance pattern is a structural pattern, and its purpose is to combine objects, not to create different products like the builder pattern.

Look at the following code:

Package facadeimport "fmt" / / initialize APIA and APIBtype APIA interface {TestA () string} func NewAPIA () APIA {return & apiRunA {} type apiRunA struct {} func (* apiRunA) TestA () string {return "An api running"} type APIB interface {TestB () string} func NewAPIB () APIB {return & apiRunB {} type apiRunB struct {} func (* apiRunB) TestB () string {return "B api running"} / appearance Class type API interface {Test () string } func NewAPI () API {return & apiRun {a: NewAPIA () B: NewAPIB (),}} type apiRun struct {an APIA b APIB} func (a * apiRun) Test () string {aRet: = a.a.TestA () bRet: = a.b.TestB () return fmt.Sprintf ("% s\ n% s", aRet, bRet)}

Assuming that we want to initialize APIA and APIB, we can handle it through an appearance class API, executing the class TestA method and the TestB method in the appearance class interface Test method, respectively.

Test the code:

Package facadeimport "testing" var expect = "An api running\ nB api running" / / TestFacadeAPI... func TestFacadeAPI (t * testing.T) {api: = NewAPI () ret: = api.Test () if ret! = expect {t.Fatalf ("expect% s, return% s", expect, ret)}} share meta mode Flyweight problem

In some cases, the program does not have enough memory capacity to store a large number of objects, or a large number of objects store duplicate states, which will result in a waste of memory resources.

The shared meta pattern proposes a solution: if the same state in multiple objects can be shared, more objects can be loaded in limited memory capacity.

Solve

As mentioned above, the shared meta-pattern wants to extract repetitive states that can be shared among multiple objects.

We can use the map structure to implement this idea, assuming that we need to store some objects that represent colors, which can be done using the shared meta mode, see the following code:

Package flyweightimport "fmt" / / Hengyuan Factory type ColorFlyweightFactory struct {maps map [string] * ColorFlyweight} var colorFactory * ColorFlyweightFactoryfunc GetColorFlyweightFactory () * ColorFlyweightFactory {if colorFactory = = nil {colorFactory = & ColorFlyweightFactory {maps: make (map [string] * ColorFlyweight) } return colorFactory} func (f * ColorFlyweightFactory) Get (filename string) * ColorFlyweight {color: = f.maps [filename] if color = = nil {color = NewColorFlyweight (filename) f.maps [filename] = color} return color} type ColorFlyweight struct {data string} / / Storage color object func NewColorFlyweight (filename string) * ColorFlyweight {/ / Load color file data: = fmt.Sprintf ("color data% s", filename) return & ColorFlyweight {data: data Type ColorViewer struct {* ColorFlyweight} func NewColorViewer (name string) * ColorViewer {color: = GetColorFlyweightFactory () .Get (name) return & ColorViewer {ColorFlyweight: color,}}

We define a shared meta-factory that uses map to store the value of the same object (key). This sharing factory can make it more convenient and secure for us to access all kinds of sharing elements and ensure that their status will not be modified.

We define the NewColorViewer method, which calls the Get method of the shared meta factory to store the object, and as you can see in the implementation of the shared meta factory, objects in the same state will only be occupied once.

Test the code:

Package flyweightimport "testing" func TestFlyweight (t * testing.T) {viewer1: = NewColorViewer ("blue") viewer2: = NewColorViewer ("blue") if viewer1.ColorFlyweight! = viewer2.ColorFlyweight {t.Fail ()}}

When a program needs to store a large number of objects and does not have enough memory, you can consider using shared meta-mode.

Agent mode Proxy problem

If you need to have a role like a "proxy" when accessing an object, she can perform cache checking, permission judgment and other access control for you before accessing the object, and perform result caching, logging and other result processing for you after accessing the object, then you can consider using proxy mode.

Recall some router modules of the web framework. When the client accesses an interface, the router module performs some prior operations, permission determination and other operations before the final execution of the corresponding interface, and logs are recorded after execution. This is a typical proxy mode.

Solve

The proxy pattern requires a proxy class that contains the member variables required to execute the real object, and the proxy class manages the entire lifecycle.

Look at the following code:

Package proxyimport "fmt" type Subject interface {Proxy () string} / / proxy type Proxy struct {real RealSubject} func (p Proxy) Proxy () string {var res string / / before calling the real object, check the cache, determine permissions, etc. P.real.Pre () / / call the real object p.real.Real () / / the operation after calling the real object, such as caching the result, processing the result Wait, p.real.After () return res} / / Real object type RealSubject struct {} func (RealSubject) Real () {fmt.Print ("real")} func (RealSubject) Pre () {fmt.Print ("pre:")} func (RealSubject) After () {fmt.Print (": after")}

We define the proxy class Proxy. After executing Proxy, before calling the real object Real, we will first call the prior object Pre, and after executing the real object Real, we will call the post object After.

Test the code:

Package proxyfunc ExampleProxy () {var sub Subject sub = & Proxy {} sub.Proxy () / Output: / / pre:real:after} Behavioral pattern

The behavioral pattern handles communication between objects and classes and enables them to maintain efficient communication and delegation.

Chain of Responsibility problem of chain of responsibility model

Suppose we want the program to follow the specified steps, and the order of the steps is not fixed, but can be changed according to different requirements, each step will do some processing to the request, and pass the result to the processor of the next step, just like an assembly line, how can we achieve it?

When faced with the requirement that multiple processors must be executed sequentially, and the order of processors can be changed, we can consider using the chain of responsibility pattern.

Solve

The responsibility chain pattern uses a structure similar to a linked list. Look at the following code:

Package chainimport "fmt" type department interface {execute (* Do) setNext (department)} type aPart struct {next department} func (r * aPart) execute (p * Do) {if p.aPartDone {fmt.Println ("aPart done") r.next.execute (p) return} fmt.Println ("aPart") p.aPartDone = true r.next.execute (p)} func (r * aPart) setNext (next department) {r.next = next} type BPart struct {next department} func (d * bPart) execute (p * Do) {if p.bPartDone {fmt.Println ("bPart done") d.next.execute (p) return} fmt.Println ("bPart") p.bPartDone = true d.next.execute (p)} func (d * bPart) setNext (next department) {d.next = next} type endPart struct {next department} func (c * endPart) execute (p * Do) { If p.endPartDone {fmt.Println ("endPartDone")} fmt.Println ("endPart")} func (c * endPart) setNext (next department) {c.next = next} type Do struct {aPartDone bool bPartDone bool endPartDone bool}

We implement the methods execute and setNext, and define three processors: aPart, bPart and endPart. Each processor can execute its corresponding business code through the execute method, and can determine who is the next processor through the setNext method. Except that endPart is the final handler, the order of processors aPart and bPart before it can be adjusted at will.

Take a look at the following test code:

Func ExampleChain () {startPart: = & endPart {} aPart: = & aPart {} aPart.setNext (startPart) bPart: = & bPart {} bPart.setNext (aPart) do: = & Do {} bPart.execute (do) / Output: / / bPart / / aPart / / endPart}

We can also adjust the execution order of the processor:

Func ExampleChain2 () {startPart: = & endPart {} bPart: = & bPart {} bPart.setNext (startPart) aPart: = & aPart {} aPart.setNext (bPart) do: = & Do {} aPart.execute (do) / / Output: / / aPart / / bPart / / endPart} Command Mode Command problem

Suppose you have achieved the function of turning on and off the TV, as the business iterates, you also need to turn on and off the refrigerator, turn on and off the lights, and turn on and off the microwave oven. These functions are based on your base class, on and off. If you make changes to the base class later, it is likely to affect other functions, which makes the project unstable.

A good design often focuses on the layering and decoupling of the software. The command mode attempts to decouple the command from the corresponding function and parameterize its method according to different requests.

Solve

Let's take the example of turning on and off household appliances. Look at the following code:

Package commandimport "fmt" / / requester type button struct {command command} func (b * button) press () {b.command.execute ()} / / specific command interface type command interface {execute ()} type onCommand struct {device device} func (c * onCommand) execute () {c.device.on ()} type offCommand struct {device device} func (c * offCommand) execute () {c.device.off ()} / / recipient type Device interface {on () off ()} type tv struct {} func (t * tv) on () {fmt.Println ("Turning tv on")} func (t * tv) off () {fmt.Println ("Turning tv off")} type airConditioner struct {} func (t * airConditioner) on () {fmt.Println ("Turning air conditioner on")} func (t * airConditioner) off () {fmt.Println ("Turning air conditioner off")}

We implement the requester button, the command interface command and the receiver device respectively. The requester button is like the remote control that can be turned on or off, while the command interface command is an intermediate layer that decouples our requester and receiver.

Test the code:

Package commandfunc ExampleCommand () {Tv () AirConditioner () / / Output: / / Turning tv on / / Turning tv off / / Turning air conditioner on / / Turning air conditioner off} func Tv () {tv: = & tv {} onTvCommand: = & onCommand {device: tv,} offTvCommand: = & offCommand {device: tv,} onTvButton: = & button {command: onTvCommand,} onTvButton.press () offTvButton: = & button {command: offTvCommand } offTvButton.press ()} func AirConditioner () {airConditioner: = & airConditioner {} onAirConditionerCommand: = & onCommand {device: airConditioner,} offAirConditionerCommand: = & offCommand {device: airConditioner,} onAirConditionerButton: = & button {command: onAirConditionerCommand,} onAirConditionerButton.press () offAirConditionerButton: = & button {command: offAirConditionerCommand,} offAirConditionerButton.press ()} iterator pattern Iterator problem

The iterator pattern is used to traverse the elements in the collection, regardless of the data structure of the collection.

Solve

Look at the following code:

Package iterator// collection interface type collection interface {createIterator () iterator} / / specific collection type part struct {title string number int} type partCollection struct {part parts [] * part} func (u * partCollection) createIterator () iterator {return & partIterator {parts: u.parts Type iterator interface {hasNext () bool getNext () * part} / / specific iterator type partIterator struct {index int parts [] * part} func (u * partIterator) hasNext () bool {if u.index < len (u.parts) {return true} return false} func (u * partIterator) getNext () * part {if u.hasNext () {part: = u.parts [u.index] u.indexation + return part} return nil}

Test the code:

Func ExampleIterator () {part1: = & part {title: "part1", number: 10,} part2: = & part {title: "part2", number: 20,} part3: = & part {title: "part3", number: 30,} partCollection: = & partCollection {parts: [] * part {part1, part2, part3} } iterator: = partCollection.createIterator () for iterator.hasNext () {part: = iterator.getNext () fmt.Println (part)} / / Output: / / & {part1 10} / & & {part2 20} / / & {part3 30}} intermediary Mode Mediator problem

The intermediary model attempts to solve the complex association of mesh relations and reduce the coupling between objects.

For example, if the cars at an intersection are all objects, they will perform different operations and go to different destinations, then the traffic police commanding at the intersection are "intermediaries".

Each object executes the intermediary interface, and then the intermediary maintains the relationship between the objects. This makes the object more independent and more suitable for use in cases where the object is a mesh relationship.

Solve

Suppose there are three senders, p1 and p2, the message sent by p1 can be received by p2, the message sent by p2 can be received by p1, and the message sent by p3 can be received by p1 and p2, how can it be realized? In a case like this, it is suitable to be implemented in the intermediary pattern.

Look at the following code:

Package mediatorimport ("fmt") type p1 struct {} func (p * p1) getMessage (data string) {fmt.Println ("p1 get message:" + data)} type p2 struct {} func (p * p2) getMessage (data string) {fmt.Println ("p2 get message:" + data)} type p3 struct {} func (p * p3) getMessage (data string) {fmt.Println ("p3 get message:" + data)} type Message struct {p1 * p2 * P2 p3 * p3} func (m * Message) sendMessage (I interface {}) Data string) {switch I. (type) {case * p1: m.p2.getMessage (data) case * p2: m.p1.getMessage (data) case * p3: m.p1.getMessage (data) m.p2.getMessage (data)}}

We define three objects, p1 and p2, p3, and implement the intermediary sendMessage.

Test the code:

Package mediatorfunc ExampleMediator () {message: = & Message {} p1: = & p2 {} p3: = & p3 {} message.sendMessage (p1, "hi! my name is p1") message.sendMessage (p2, "hi! my name is p2") message.sendMessage (p3, "hi! my name is p3") / Output: / / p2 get message: hi! My name is p1 / / p1 get message: hi! My name is p2 / / p1 get message: hi! My name is p3 / / p2 get message: hi! My name is p3} memo mode Memento problem

Common text editors support saving and restoring a piece of text. What should we do if we want to save and restore a piece of text in the program?

We need to provide the ability to save and restore. When the save function is called, a snapshot of the current object is generated, and when the restore function is called, the current snapshot is overwritten with the previously saved snapshot. This can be done using memo mode.

Solve

Look at the following code:

Package mementoimport "fmt" type Memento interface {} type Text struct {content string} type textMemento struct {content string} func (t * Text) Write (content string) {t.content = content} func (t * Text) Save () Memento {return & textMemento {content: t.content,}} func (t * Text) Load (m Memento) {tm: = m. (* textMemento) t.content = tm.content} func (t * Text) Show () {fmt.Println ("content:", t.content)}

We define a textMemento structure to save the current snapshot and overwrite the snapshot to the current content in the Load method.

Test the code:

Package mementofunc ExampleText () {text: = & Text {content: "how are you",} text.Show () progress: = text.Save () text.Write ("fine think you and you") text.Show () text.Load (progress) text.Show () / Output: / / content: how are you / / content: fine think you and you / / content: how are you} Observer Mode Observer problem

If you need to be notified as its "observer" when the state of one object is changed, you can use the observer mode.

We call objects whose state changes are notified to other objects as "publishers", and objects that pay attention to changes in the state of publishers as "subscribers".

Solve

Look at the following code:

Package observerimport "fmt" / / publisher type Subject struct {observers [] Observer content string} func NewSubject () * Subject {return & Subject {observers: make ([] Observer, 0),}} / / add subscriber func (s * Subject) AddObserver (o Observer) {s.observers = append (s.observers) O)} / / change the status of the publisher func (s * Subject) UpdateContext (content string) {s.content = content s.notify ()} / / notify the subscriber interface type Observer interface {Do (* Subject)} func (s * Subject) notify () {for _, o: = range s.observers {o.Do (s)} / / subscriber type Reader struct {name string} func NewReader (name string) * Reader {return & Reader {name: name }} func (r * Reader) Do (s * Subject) {fmt.Println (r.name + "get" + s.content)}

Quite simply, all we have to do is implement a notification notify method that executes when the publisher's state changes.

Test the code:

Package observerfunc ExampleObserver () {subject: = NewSubject () boy: = NewReader ("Xiaoming") girl: = NewReader ("Xiaomei") subject.AddObserver (boy) subject.AddObserver (girl) subject.UpdateContext ("hi~") / Output: / / Xiaoming get hi~ / / Xiaomei get hi~} State Mode State problem

State mode can be used if the implementation method of an object changes according to its own state.

For example: suppose there is a method to open the door, and the state of the door is "closed" at the beginning, and you can execute the open method and the close method. When you execute the open method, the state of the door becomes "open", and then the open method does not perform the function of opening the door. Instead, it returns "the door is open". If the close method is executed, the state of the door becomes "closed", and then the close method will not perform the function of closing the door. Instead, return to "door is closed". This is a simple example, we will provide different implementation methods for each state, it is troublesome to organize these methods, what if there are more and more states? There is no doubt that this will make the code bloated.

Solve

If we need to provide open and close methods in three states for a door object:

In the "open" state, the open method returns "door is open" and the close method returns "closed successfully".

In the closed state, the open method returns Open successfully, and the close method returns door closed.

In the damaged state, the open method returns "the door is damaged and cannot be opened", and the close method returns "the door is damaged and cannot be closed".

Look at the following code:

Package stateimport "fmt" / / Interface type state interface {open (* door) close (* door)} / / Gate object type door struct {opened state closed state damaged state currentState state / / current status} func (d * door) open () {d.currentState.open (d)} func (d * door) close () {d.currentState.close (d)} func (d * door) setState (s state) {d .currentState = s} / / Open state type opened struct {} func (o * opened) open (d * door) {fmt.Println ("door opened")} func (o * opened) close (d * door) {fmt.Println ("closed successfully") / / closed status type closed struct {} func (c * closed) open (d * door) {fmt.Println ("opened successfully")} func (c * closed) close (d * Door) {fmt.Println ("door closed")} / / damage status type damaged struct {} func (a * damaged) open (d * door) {fmt.Println ("door is damaged" Cannot open ")} func (a * damaged) close (d * door) {fmt.Println (" the door is damaged and cannot be closed ")}

Our door object door implements the open and close methods, in which you only need to call the open and close methods of the current state currentState.

Test the code:

Package statefunc ExampleState () {door: = & door {} / / Open status opened: = & opened {} door.setState (opened) door.open () door.close () / closed status closed: = & closed {} door.setState (closed) door.open () door.close () / / damaged status damaged: = & damaged {} door.setState (damaged) door.open () door.close () / / Output: / / door opened / / closed successfully / / opened successfully / / door closed / / door damaged Unable to open / / door is corrupted and cannot close} policy mode Strategy problem

Suppose you need to achieve a set of travel functions, the solution can choose to walk, ride, drive, the easiest way is to implement these three methods for the client to call. But doing so makes the object and its code implementation become coupled, the client needs to decide the travel mode, and then decides to call walking, cycling, driving and other methods, which is not in line with the open-closed principle.

The difference of the policy pattern is that it will extract these travel plans into a set of classes called policies, and the client still calls the same travel object. It does not need to pay attention to the implementation details, but only needs to specify the required policies in the parameters.

Solve

Look at the following code:

Package strategyimport "fmt" type Travel struct {name string strategy Strategy} func NewTravel (name string, strategy Strategy) * Travel {return & Travel {name: name, strategy: strategy } func (p * Travel) traffic () {p.strategy.traffic (p)} type Strategy interface {traffic (* Travel)} type Walk struct {} func (w * Walk) traffic (t * Travel) {fmt.Println (t.name + "walk")} type Ride struct {} func (w * Ride) traffic (t * Travel) {fmt.Println (t.name + "ride")} type Drive struct {} func (w * Drive) traffic (t * Travel) { Fmt.Println (t.name + "drive")}

We define a set of policy interfaces of strategy and implement Walk, Ride and Drive algorithms for them. The client only needs to execute the traffic method without paying attention to the implementation details.

Test the code:

Package strategyfunc ExampleTravel () {walk: = & Walk {} Travel1: = NewTravel ("Xiaoming", walk) Travel1.traffic () ride: = & Ride {} Travel2: = NewTravel ("Xiaomei", ride) Travel2.traffic () drive: = & Drive {} Travel3: = NewTravel ("Xiaogang", drive) Travel3.traffic () / / Output: / / Xiaoming walk / / Xiaomei ride / / Xiaogang drive} template method pattern Template Method problem

The template method pattern is to decompose the algorithm into a series of steps, and then call these steps in turn in a template method. In this way, the client does not need to know the implementation details of each step, just need to call the template.

Solve

For a very simple example, look at the following code:

Package templatemethodimport "fmt" type PrintTemplate interface {Print (name string)} type template struct {isTemplate PrintTemplate name string} func (t * template) Print () {t.isTemplate.Print (t.name)} type A struct {} func (a * A) Print (name string) {fmt.Println ("a:" + name) / / Business Code. } type B struct {} func (b * B) Print (name string) {fmt.Println ("b:" + name) / / business code. }

Test the code:

Package templatemethodfunc ExamplePrintTemplate () {templateA: = & A {} template: = & template {isTemplate: templateA, name: "hi~",} template.Print () templateB: = & B {} template.isTemplate = templateB template.Print () / / Output: / / a: hi~ / / b: hi~} Visitor Mode Visitor problem

The Visitor pattern tries to solve the problem of adding new operations without changing the object structure of the class.

Solve

Look at the following code:

Package visitorimport "fmt" type Shape interface {accept (visitor)} type square struct {} func (s * square) accept (v visitor) {v.visitForSquare (s)} type circle struct {} func (c * circle) accept (v visitor) {v.visitForCircle (c)} type visitor interface {visitForSquare (* square) visitForCircle (* circle)} type sideCalculator struct {} func (a * sideCalculator) visitForSquare (s * square) {fmt.Println ("square side")} func (a * sideCalculator) VisitForCircle (s * circle) {fmt.Println ("circle side")} type radiusCalculator struct {} func (a * radiusCalculator) visitForSquare (s * square) {fmt.Println ("square radius")} func (a * radiusCalculator) visitForCircle (c * circle) {fmt.Println ("circle radius")}

Test the code:

Package visitorfunc ExampleShape () {square: = & square {} circle: = & circle {} side: = & sideCalculator {} square.accept (side) circle.accept (side) radius: = & radiusCalculator {} square.accept (radius) circle.accept (radius) / Output: / / square side / / circle side / / square radius / / circle radius} "Tao" of the design pattern

How many design patterns can you remember? The design pattern is divided into the "art" part and the "Tao" part. The above design patterns are the "art" parts, and they are some classic solutions around the core ideas of the design pattern. In other words, it is important to understand why you use those design patterns, specific problems, and specific analysis, rather than mechanically applying certain design patterns into the code.

Design patterns have six principles, and the purpose of the above design patterns is to enable the software system to achieve these principles:

Opening and closing principle

The software should be open to extensions and closed to modifications.

Extend the system without modifying the existing code. This can reduce the maintenance cost of the software, but also increase the scalability.

Richter's substitution principle

Wherever a base class can appear, a subclass must appear.

The Richter substitution principle is a supplement to the open-closed principle. The key step to realize the open-closed principle is abstraction, and the relationship between base classes and subclasses is to be as abstract as possible.

Principle of dependency inversion

Interface-oriented programming, abstraction should not depend on concrete classes, which should depend on abstractions.

This is to reduce the coupling between classes and make the system more suitable for expansion and easier to maintain.

Principle of single responsibility

There should be only one reason for a class to change.

The more a class carries, the higher the coupling. If the responsibility of the class is single, you can reduce the risk of error and improve the readability of the code.

Know the principle at least

An entity should interact with other entities as little as possible.

Or to reduce coupling, the less a class is associated with other classes, the easier it is to extend.

Interface separation principle

Use multiple specialized interfaces instead of a single highly coupled interface.

Avoid taking up too many responsibilities on the same interface, and a clearer division can reduce coupling. High coupling can make the program not easy to expand and increase the risk of error.

The above is the content of this article on "how to achieve 23 design patterns in Go language". I believe we all have some understanding. I hope the content shared by the editor will be helpful to you. If you want to know more about the relevant knowledge, please pay attention to the industry information channel.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report