Real World Go Design Pattern – Factory Pattern

Original link: https://colobu.com/2023/07/24/go-design-patterns-factory/

The factory pattern is a creational pattern, which is a design pattern used to create new objects.

The 23 design patterns include the abstract factory pattern, the factory method pattern, and others have also summed up the simple factory pattern. This factory relies heavily on interfaces, abstract classes, and concrete class implementations. In Go, there is no such complicated factory creation mode. The most common factory mode in Go is similar to the simple factory mode, and is generally implemented through New or NewXXX .

For example, if we want to implement a storage data structure, it may be a memory-based storage, a disk-based storage, or a temporary file-based storage. In any case, we first define a Store interface:


1
2
3
4
5
6
7

package data
import “io”
type Store interface {
Open( string ) (io. ReadWriteCloser, error)
}

Then define different Store implementations:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

package data
type StorageType int
const (
DiskStorage StorageType = 1 << iota
TempStorage
Memory Storage
)
func NewStore(t StorageType) Store {
switch t {
case MemoryStorage:
return newMemoryStorage( /*…*/ )
case DiskStorage:
return newDiskStorage( /*…*/ )
default :
return newTempStorage( /*…*/ )
}
}

The method of use is as follows:


1
2
3
4
5

s, _ := data. NewStore(data. MemoryStorage)
f, _ := s. Open( “file” )
n, _ := f.Write([] byte ( “data” ))
defer f. Close()

(The above example is taken from https://github.com/tmrts/go-patterns )

Furthermore, we won’t even create an interface, such as net/http.NewRequestWithContext of the Go standard library, to create a *http.Request object.

According to different body types, it will create different request.GetBody , no interface is used here, a struct is enough, because GetBody is a function pointer, you can generate different Requests according to different parameters. This makes full use of Go’s type switch, func pointer and other features, without generating complex interfaces and concrete classes.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

func NewRequestWithContext(ctx context.Context, method, url string , body io.Reader) (*Request, error) {
u.Host = removeEmptyPort(u.Host)
req := &Request{
ctx: ctx,
Method: method,
URL: u,
Proto: “HTTP/1.1” ,
ProtoMajor: 1 ,
ProtoMinor: 1 ,
Header: make (Header),
Body: rc,
Host: u.Host,
}
if body != nil {
switch v := body.( type ) {
case *bytes. Buffer:
req. ContentLength = int64 (v. Len())
buf := v.Bytes()
req. GetBody = func () (io. ReadCloser, error) {
r := bytes. NewReader(buf)
return io.NopCloser(r), nil
}
case *bytes. Reader:
req. ContentLength = int64 (v. Len())
snapshot := *v
req. GetBody = func () (io. ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
case *strings. Reader:
req. ContentLength = int64 (v. Len())
snapshot := *v
req. GetBody = func () (io. ReadCloser, error) {
r := snapshot
return io.NopCloser(&r), nil
}
default :
}
if req.GetBody != nil && req.ContentLength == 0 {
req.Body = NoBody
req. GetBody = func () (io. ReadCloser, error) { return NoBody, nil }
}
}
return req, nil
}

A better example is Open方法under database/sql :


1

func Open(driverName, dataSourceName string ) (*DB, error)

You need to provide different database type names and dcn to generate a corresponding *DB object. Note that DB is a struct and does not define a DB type interface.

Its specific implementation is as follows:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

func Open(driverName, dataSourceName string ) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil , fmt.Errorf( “sql: unknown driver %q (forgotten import?)” , driverName)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx. OpenConnector(dataSourceName)
if err != nil {
return nil , err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}

It will find the corresponding factory (driver) from a table ( drivers ), then call OpenConnector of this factory to get a connector (or directly generate a dsnConnector), and finally call OpenDB to create a DB object.

Different database types can register specific database drivers through Register(name string, driver driver.Driver) , such as mysql drivers:


1
2
3

func init() {
sql.Register( “mysql” , &MySQLDriver{})
}

clickhouse driver:


1
2
3
4

func init() {
var debugf = func (format string , v …any) {}
sql.Register( “clickhouse” , &stdDriver{debugf: debugf})
}

For this kind of scenario with different specific implementations, Go’s routine often uses a table to register different implementations, and searches to find the corresponding implementation when creating. If the rpcx microservice framework supports different connection protocols, it also finds the corresponding creation method to create a connection by searching:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one

func init() {
makeListeners[ “tcp” ] = tcpMakeListener( “tcp” )
makeListeners[ “tcp4” ] = tcpMakeListener( “tcp4” )
makeListeners[ “tcp6” ] = tcpMakeListener( “tcp6” )
makeListeners[ “http” ] = tcpMakeListener( “tcp” )
makeListeners[ “ws” ] = tcpMakeListener( “tcp” )
makeListeners[ “wss” ] = tcpMakeListener( “tcp” )
}
func (s *Server) makeListener(network, address string ) (ln net.Listener, err error) {
ml := makeListeners[network]
if ml == nil {
return nil , fmt.Errorf( “can not make listener for %s” , network)
}
if network == “wss” && s.tlsConfig == nil {
return nil , errors.New( “must set tlsconfig for wss” )
}
return ml(s, address)
}

This article is transferred from: https://colobu.com/2023/07/24/go-design-patterns-factory/
This site is only for collection, and the copyright belongs to the original author.