工厂模式(Factory pattern)是一种创建型模式,就是用来创建新对象的一种设计模式,它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
23种设计模式中包含抽象工厂模式,工厂方法模式,其他也有人总结出简单工厂模式。这个工厂大量的依赖接口、抽象类和具体的类实现。在Go中,才不会有这么复杂的工厂创建模式,Go中最常见的工厂模式类似简单工厂模式,而且一般都是通过New
或者NewXXX
来实现。
比如我们要实现一个存储数据结构,它可能是基于内存的存储,也可能是一个基于磁盘的存储,抑或者是一个基于临时文件的存储,不管怎么样,我们先定义一个Store
接口:
1 2 3 4 5 6 7
| package data import "io" type Store interface { Open(string) (io.ReadWriteCloser, error) }
|
再定义不同的Store实现:
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 MemoryStorage ) func NewStore(t StorageType) Store { switch t { case MemoryStorage: return newMemoryStorage( ) case DiskStorage: return newDiskStorage( ) default: return newTempStorage( ) } }
|
使用方法如下:
1 2 3 4 5
| s, _ := data.NewStore(data.MemoryStorage) f, _ := s.Open("file") n, _ := f.Write([]byte("data")) defer f.Close()
|
(以上例子摘自https://github.com/tmrts/go-patterns)
更进一步,甚至我们都不会创建一个接口,比如Go标准库的net/http.NewRequestWithContext
,用来创建一个*http.Request
对象。
根据body类型的不同它会创建不同的request.GetBody
,这里没有使用接口,一个struct足够了,因为GetBody
是一个函数指针,你可以根据参数的不同生成不同的Request。这里充分利用了Go的type switch、func pointer等特性,不用生成复杂的接口和具体类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 }
|
一个更好的例子就是database/sql
下的Open方法
:
1
| func Open(driverName, dataSourceName string) (*DB, error)
|
你子需要提供不同的数据库类型名,以及dcn,就能生成一个对应的*DB
对象,注意DB是struct,并没有定义一个 DB
类型的接口。
它的具体实现是这样的:
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 }
|
它会从一个表(drivers
)找到对应的工厂(driver), 然后调用这个工厂的OpenConnector
得到一个连接器(或者直接生成一个dsnConnector),最后调用OpenDB
创建DB对象。
不同的数据库类型可以通过Register(name string, driver driver.Driver)
注册特定的数据库驱动,比如mysql的驱动:
1 2 3
| func init() { sql.Register("mysql", &MySQLDriver{}) }
|
clickhouse驱动:
1 2 3 4
| func init() { var debugf = func(format string, v ...any) {} sql.Register("clickhouse", &stdDriver{debugf: debugf}) }
|
针对这种有具体的不同实现的场景,Go的套路经常是用一个表来注册不同的实现,创建的时候查找找到相应的实现方式。如果rpcx微服务框架在支持不同的连接协议时,也是通过查找找到相应的创建方法创建连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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) }
|