工厂模式(Factory pattern)是一种创建型模式,就是用来创建新对象的一种设计模式,它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
23种设计模式中包含抽象工厂模式,工厂方法模式,其他也有人总结出简单工厂模式。这个工厂大量的依赖接口、抽象类和具体的类实现。在Go中,才不会有这么复杂的工厂创建模式,Go中最常见的工厂模式类似简单工厂模式,而且一般都是通过New或者NewXXX来实现。
比如我们要实现一个存储数据结构,它可能是基于内存的存储,也可能是一个基于磁盘的存储,抑或者是一个基于临时文件的存储,不管怎么样,我们先定义一个Store接口:
1 2 3 4 5 6 7 package dataimport "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 datatype 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) }