Previous: [Go implementation] practice 23 design modes of GoF: Builder mode Simple distributed application system (example code project): https://github.com/ruanrunxue/Practice-Design-Pattern--Go-Implementation
sketch
Similar to the builder pattern discussed in the previous article, the Factory Method Pattern encapsulates the logic of object creation and provides users with a simple and easy-to-use object creation interface. The two are slightly different in application scenarios. The builder mode is often used in scenarios where multiple parameters need to be passed for instantiation; The Factory Method Pattern is often used in scenarios where objects are created without specifying their specific types.
UML structure

code implementation
Example
stay Simple distributed application system In the example code project, we designed the Sidecar side car module. The function of Sidecar is to add additional functions to the native Socket, such as flow control, logging, etc.

The Sidecar module uses the decorator mode to decorate the Socket. So the client actually uses Sidecar as a Socket, for example:
copy// demo/network/http/http_client.go package http // Create a new HTTP client with the Socket interface as the input parameter func NewClient(socket network.Socket, ip string) (*Client, error) { ... // Some initialization logic return client, nil } // When using the NewClient, we can pass in Sidecar to add additional flow control functions to the Http client client, err := http.NewClient(sidecar.NewFlowCtrlSidecar(network.DefaultSocket()), "192.168.0.1")
In the service message mediation, HTTP is called every time an HTTP request from an upstream service is received Newclient to create an HTTP client and forward requests to downstream services through it:
copytype ServiceMediator struct { ... server *http.Server } // Forward forward forward the request. The request URL is in the form of /{serviceType}+ServiceUri, such as /serviceA/api/v1/task func (s *ServiceMediator) Forward(req *http.Request) *http.Response { ... // Discover the destination IP address of downstream services dest, err := s.discovery(svcType) // Create an HTTP client and hard code sidecar NewFlowCtrlSidecar(network.DefaultSocket()) client, err := http.NewClient(sidecar.NewFlowCtrlSidecar(network.DefaultSocket()), s.localIp) // Forward request through HTTP client resp, err := client.Send(dest, forwardReq) ... }
In the above implementation, we are calling http The Sidecar Newflowctrlsidecar (network.defaultsocket()) is hard coded. If you want to extend the Sidecar in the future, you must modify the code logic, which violates the Opening and closing Principle OCP.
Experienced students may think that you can call http Newclient takes the Socket interface as the input parameter; Then, when the ServiceMediator is initialized, inject a specific type of Sidecar into the ServiceMediator:
copytype ServiceMediator struct { ... server *http.Server // Depend on Socket abstract interface socket network.Socket } // Forward forward forward the request. The request URL is in the form of /{serviceType}+ServiceUri, such as /serviceA/api/v1/task func (s *ServiceMediator) Forward(req *http.Request) *http.Response { ... // Discover the destination IP address of downstream services dest, err := s.discovery(svcType) // Create an HTTP client and use the s.socket abstract interface as the input parameter client, err := http.NewClient(s.socket, s.localIp) // Forward request through HTTP client resp, err := client.Send(dest, forwardReq) ... } // When ServiceMediator initializes, inject Sidecar of specific type into ServiceMediator mediator := &ServiceMediator{ socket: sidecar.NewFlowCtrlSidecar(network.DefaultSocket()) }
The above modification has changed from relying on concrete to relying on abstraction, which conforms to the principle of opening and closing.
However, the Forward method has a scenario of concurrent calls, so it hopes to create a new Socket/Sidecar to complete network communication every time it is called. Otherwise, it needs to be locked to ensure concurrency security. The above modifications will cause the same Socket/Sidecar to be used in the life cycle of ServiceMediator, which obviously does not meet the requirements.
Therefore, we need a method that can not only meet the opening and closing principle, but also create a new Socket/Sidecar instance each time the Forward method is called. The factory method pattern can just meet these two requirements. Let's use it to optimize the code.
realization
copy// demo/sidecar/sidecar_factory.go // Key point 1: define a Sidecar factory abstract interface type Factory interface { // Key point 2: factory method returns Socket abstract interface Create() network.Socket } // Key point 3: implement specific plants as required // demo/sidecar/raw_socket_sidecar_factory.go // RawSocketFactory only has the sidecar with the function of native socket and implements the Factory interface type RawSocketFactory struct { } func (r RawSocketFactory) Create() network.Socket { return network.DefaultSocket() } // demo/sidecar/all_in_one_sidecar_factory.go // AllInOneFactory is a sidecar Factory with all functions and implements the Factory interface type AllInOneFactory struct { producer mq.Producible } func (a AllInOneFactory) Create() network.Socket { return NewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()), a.producer) }
In the above code, we define a Factory abstract interface Factory, and have two concrete implementations, RawSocketFactory and allionefactory. Finally, ServiceMediator relies on Factory and creates a new Socket/Sidecar through Factory in the Forward method:
copy// demo/service/mediator/service_mediator.go type ServiceMediator struct { ... server *http.Server // Key point 4: the client depends on the Factory abstract interface sidecarFactory sidecar.Factory } // Forward forward forward the request. The request URL is in the form of /{serviceType}+ServiceUri, such as /serviceA/api/v1/task func (s *ServiceMediator) Forward(req *http.Request) *http.Response { ... // Discover the destination IP address of downstream services dest, err := s.discovery(svcType) // Create an HTTP client and call sidecarfactory Create() generates a Socket as an input parameter client, err := http.NewClient(s.sidecarFactory.Create(), s.localIp) // Forward request through HTTP client resp, err := client.Send(dest, forwardReq) ... } // Key point 5: when ServiceMediator is initialized, the sidecar Factory injection into ServiceMediator mediator := &ServiceMediator{ sidecarFactory: &AllInOneFactory{} // sidecarFactory: &RawSocketFactory{} }
The following is a summary of the key points to realize the factory method mode:
- Define a factory method abstract interface, such as sidecar Factory.
- In the factory method, return the object / interface to be created, such as network Socket. The factory method is usually named Create.
- Define the concrete implementation objects of the factory method abstract interface according to specific needs, such as RawSocketFactory and allionefactory.
- When used by the client, it depends on the factory method abstract interface.
- In the client initialization phase, complete the dependency injection of specific factory objects.
extend
Go style implementation
The factory method pattern implementation mentioned above is a very typical object-oriented style. Next, we give a more Go style implementation.
copy// demo/sidecar/sidecar_factory_func.go // Key point 1: define Sidecar factory method type type FactoryFunc func() network.Socket // Key point 2: define the specific factory method implementation as required. Note that the factory method defined here is the factory method of the factory method, and the factory method type returned is the FactoryFunc factory method type func RawSocketFactoryFunc() FactoryFunc { return func() network.Socket { return network.DefaultSocket() } } func AllInOneFactoryFunc(producer mq.Producible) FactoryFunc { return func() network.Socket { return NewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()), producer) } } type ServiceMediator struct { ... server *http.Server // Key point 3: client depends on FactoryFunc factory method type sidecarFactoryFunc FactoryFunc } func (s *ServiceMediator) Forward(req *http.Request) *http.Response { ... dest, err := s.discovery(svcType) // Key point 4: create an HTTP client and call sidecarFactoryFunc() to generate a Socket as an input parameter client, err := http.NewClient(s.sidecarFactoryFunc(), s.localIp) resp, err := client.Send(dest, forwardReq) ... } // Key 5: when ServiceMediator initializes, inject FactoryFunc of specific type into ServiceMediator mediator := &ServiceMediator{ sidecarFactoryFunc: RawSocketFactoryFunc() // sidecarFactory: AllInOneFactoryFunc(producer) }
The above implementation makes use of the characteristics of the function as a first-class citizen in the Go language, with fewer interface s and struct s defined, and the code is more concise.
Several key implementation points are similar to the implementation of object-oriented style. It is worth noting that key point 2 is that we have defined a factory method of a factory method. This is to use the characteristics of function closures to pass parameters. If factory methods are defined directly, AllInOneFactoryFunc is implemented as follows, and polymorphism cannot be implemented:
copy// Not FactoryFunc type, cannot implement polymorphism func AllInOneFactoryFunc(producer mq.Producible) network.Socket { return NewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()), producer) }
Simple factory
Another variant of the factory method pattern is the simple factory. Instead of polymorphism, it uses simple switch case / if else criteria to determine which product to create:
copy// demo/sidecar/sidecar_simple_factory.go // Key 1: define sidecar type type Type uint8 // Key point 2: define sidecar specific types as required const ( Raw Type = iota AllInOne ) // Key 3: define simple factory objects type SimpleFactory struct { producer mq.Producible } // Key point 4: define factory methods, input parameters as sidecar types, and create products according to switch case or if else func (s SimpleFactory) Create(sidecarType Type) network.Socket { switch sidecarType { case Raw: return network.DefaultSocket() case AllInOne: return NewAccessLogSidecar(NewFlowCtrlSidecar(network.DefaultSocket()), s.producer) default: return nil } } // Key point 5: when creating a product, a specific sidecar type is passed in, such as sidecar AllInOne simpleFactory := &sidecar.SimpleFactory{producer: producer} sidecar := simpleFactory.Create(sidecar.AllInOne)
Static factory method
The static factory method is a Java/C++ expression. It is mainly used to replace the constructor to complete the instantiation of objects. It can make the code more readable and decouple from the client. For example, the static factory method of Java is implemented as follows:
copypublic class Packet { private final Endpoint src; private final Endpoint dest; private final Object payload; private Packet(Endpoint src, Endpoint dest, Object payload) { this.src = src; this.dest = dest; this.payload = payload; } // Static factory method public static Packet of(Endpoint src, Endpoint dest, Object payload) { return new Packet(src, dest, payload); } ... } // usage packet = Packet.of(src, dest, payload)
There is no static statement in Go. You can directly complete the construction of objects through ordinary functions, such as:
copy// demo/network/packet.go type Packet struct { src Endpoint dest Endpoint payload interface{} } // Factory method func NewPacket(src, dest Endpoint, payload interface{}) *Packet { return &Packet{ src: src, dest: dest, payload: payload, } } // usage packet := NewPacket(src, dest, payload)
Typical application scenarios
- When the object instantiation logic is complex, you can choose to use the factory method pattern / simple factory / static factory method for encapsulation, providing an easy-to-use interface for the client.
- If the instantiated object / interface involves multiple implementations, you can use the factory method pattern to implement polymorphism.
- For the creation of common objects, it is recommended to use the static factory method, which has better readability and low coupling than direct instantiation (such as &packet{src: SRC, dest: DeST, payload: payload}).
Advantages and disadvantages
advantage
- The code is more readable.
- It is decoupled from the client program. When the instantiation logic changes, it only needs to change the factory method, avoiding the shotgun modification.
shortcoming
- The introduction of factory method pattern will add some object / interface definitions, and misuse will lead to more complex code.
Association with other modes
Many students tend to confuse the factory method pattern with the abstract factory pattern. The abstract factory pattern is mainly used in the scenario of instantiating "product family", which can be regarded as an evolution of the factory method pattern.