具体的问题可以参考 这个 ,里面有比较详细的背景介绍。
大概就是说,在项目中的时候,常常会遇到这种情况,一个backend的接口,之后多个实际的backend的实现,一个driver的接口,多个实际类型的driver的实现,那么这种场景下的代码应该如何处理?
因为golang中没有动态加载的这种模式,就是传递一个具体的driver名称过来,接下来怎么根据这个指定的driver名称来加载上对应的实际的struct呢?
最近正好有看一点registry的源码,下面具体分析下在registry中这个工厂模式是怎么使用的。
这里采用的源码tag是v1.11.0
比如registry backend的设计,在最外层是storagedriver的接口:
type StorageDriver interface { ... Name() string List(ctx context.Context, path string) ([]string, error) Stat(ctx context.Context, path string) (FileInfo, error) ...}
之后在第二层的目录中,每个目录都是具体的driver实现,实现了上面的StorageDriver接口。比如azure,inmemory,s3等等。当然还有好多别的内容,等下介绍。
下面关键是看如何将这两部分连接起来。
在一个factory的package中,有一个 StorageDriverFactory
的interface。还有一个 register
和 create
的function,下面来详细看看。
type StorageDriverFactory interface {Create(parameters map[string]interface{})(storagedriver.StorageDriver, error)}
可以看到,StorageDriverFactory接口中只有一个Create方法,传入参数是一个map,返回的是一个实现了StorageDriver接口类型的实例。
在这个package内部存放这一个map
var driverFactories = make(map[string]StorageDriverFactory)
其中key值是storagedriver的名称,value值是对应的实现了StorageDriverFactory接口的实例。
可以看到,factory中的register方法就是把实例注册到上面提到的driverFactories的map中,传入的参数是一个name以及一个StorageDriverFactory实例。
func Register(name string, factory StorageDriverFactory) { if factory == nil { panic("Must not provide nil StorageDriverFactory") } _, registered := driverFactories[name] if registered { panic(fmt.Sprintf("StorageDriverFactory named %s already registered", name)) } driverFactories[name] = factory}
再看一下Create方法:
func Create(name string, parameters map[string]interface{}) (storagedriver.StorageDriver, error) { driverFactory, ok := driverFactories[name] if !ok { return nil, InvalidStorageDriverError{name} } return driverFactory.Create(parameters)}
首先是根据传入进来的name从directories中的都具体的driverFactory实例,之后在调用对应实例的Create方法,传入的值是一个map,这个应该是传入的参数,从这里可以看到,只有每个driver首先被注册到directory中,之后才能被create。
那么它们是如何被注册进去的呢?
下面来看看,每个对应的driver中,Create方法是如何实现的,当每个backend都有自己不同的逻辑,比如以最简单的s3为例,在对应的包中有init方法,包被应用的时候,init方法首先会被调用:
type s3DriverFactory struct{} func init() { for _, region := range []string{ "us-east-1", "us-west-1", "us-west-2", "eu-west-1", "eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1", "ap-northeast-2", "sa-east-1", "cn-north-1", } { validRegions[region] = struct{}{} } // Register this as the default s3 driver in addition to s3aws factory.Register("s3", &s3DriverFactory{}) factory.Register(driverName, &s3DriverFactory{})}
核心就是调用factory的register方法,把s3DriverFactory类型注册到map中,之后最关键的部分就是s3DriverFactory实现的这个Create方法,返回的就是最外层的那个实现了StorageDriver接口的实例。
也就是说,在具体使用的时候,只要通过factory的Create方法,并且传入对应的driver name和相关的parameters,就可以得到一个对应的driver了,这个过程对外呈现的就是一个统一的factory Create方法,就是所谓的工厂模式的实现,是指上是将不同struct的Create方法注册在了一个map中,这个Create方法被一个 driverFactory
的接口包装了起来。
这里采用的源码tag是v0.20.0
开始的时候这一块看的似懂非懂,比较凌乱,一旦熟悉了工厂模式的思路,这一块就变得有章可寻,真是清晰明了了好多。
cadvisor这块的工厂模式,在实现上,比上面介绍的registry中的方式更复杂一点,由于container部分可是不同类型的driver,比如docker的,直接libcontainer的,也是多种driver做相同事情的不同实现,所以用工厂模式是很合适的。
首先是在最外层 cadvisor/container/factory.go
中有一个 factories []ContainerHandlerFactory
的数组,具体的factory都是往这个数组中注册的。其中有一个注册的函数,就是把新的factory append到这个数组的后面,这个套路应该很熟了。
在第二层目录中,有docker,raw 等多种方式的实现,也就是说,通过factory模式抽象的,是这三种具体的driver。
在上面提到的registry的实现中,每个子模块的工厂实例只有一个Create方法,我们看下cadvisor这部分中,对应的工厂实例,都有哪些方法
type ContainerHandlerFactory interface { // Create a new ContainerHandler using this factory. CanHandleAndAccept() must have returned true. NewContainerHandler(name string, inHostNamespace bool) (c ContainerHandler, err error) // Returns whether this factory can handle and accept the specified container. CanHandleAndAccept(name string) (handle bool, accept bool, err error) // Name of the factory. String() string // Returns debugging information. Map of lines per category. DebugInfo() map[string][]string}
可以看到,信息更详细了些,但是本质还是不变的,第一个方法是创建一个新的实例,返回的对象是实现了 ContainerHandler
接口的实例,这个是通过接口模式所要交付出来的内容。 后面的方法是一些用于辅助的,比如返回这个factory的名字,这个factory是否能够处理指定的container,以及Debug信息。
每个driver中对于Factory接口都有不同的实现,主要是create这部分。比如docker的package,最后返回的是dockerContainerHandler,这个handler中实现了ContainerHandler中的全部方法,具体的实现细节不是这块的重点。
之后factory模式怎么使用的呢?按照前一部分的介绍,一定是在某个地方,先把一些factory的具体实例注册到数组中,然后在具体使用的时候,再根据一些实际穿入的key字段取到对应的factory,之后再进行create操作。
这里的factory模式的使用有点变化,关键在于 NewContainerHandler
的操作,这个函数的主要功能就是通过检索之前注册进来的factory实例,然后调用其中对应的Create方法,生成新的Handler,与上一部分registry的工厂模式比较,可以发现,这里存储工厂实例的结构是一个Array,而通常意义上的应该是一个map才对,通过key值来检索到对应的工厂实例。
这里做的额外操作就是自动从数组中获取对应的工厂实例,逻辑也很简单:
for _, factory := range factories { canHandle, canAccept, err := factory.CanHandleAndAccept(name) if err != nil { glog.V(4).Infof("Error trying to work out if we can handle %s: %v", name, err) } if canHandle { if !canAccept { glog.V(3).Infof("Factory %q can handle container %q, but ignoring.", factory, name) return nil, false, nil } glog.V(3).Infof("Using factory %q for container %q", factory, name) handle, err := factory.NewContainerHandler(name, inHostNamespace) return handle, canAccept, err } else { glog.V(4).Infof("Factory %q was unable to handle container %q", factory, name) } }
对factories数组进行遍历,调用 factory.CanHandleAndAccept
来判断这个factory是否可以处理传入的container name 如果可以,则调用对应的factory实例中的create方法,得到对应的handler,具体的判断逻辑这里不多赘述,但是看看之后,再做类似项目的时候,感觉还是很有意义的,毕竟都是一些类似的操作。
因此,在往factories数组中注册对应的实例的时候,也有逻辑的层面在里面,因为如果两种factory同时满足的话,第一次被检索到的factory总是先被使用(个人总感觉这块这样实现的不是太好,还是按照存map的形式,会比较清晰)
下面看看注册factory实例的部分,由于这个factory的方法本身比较多,要实现这些方法,需要一些对应的组件,所以在factory注册的时候,不像是上面介绍的registry中的实现那样,直接注册一个&FactoryInstance{}过去这么简单,因为在创建对应的FactoryInstance的时候,需要有一些额外的操作要做,因此,每个driver的注册部分被单独抽象出来了一个函数 docker.Register
以及 raw.Register
。它们的主要工作就是准备对应FactoryInstance所需要的字段,创建FactoryInstance,注册到上面提到的factories数组中。
最后看下注册的部分,一种是通过init函数的方式实现,就像registry中的那样。另外一种就是在manager组件启动的时候,手动注册,比如像cadvisor中的这样:
// Start the container manager.func (self *manager) Start() error { // Register Docker container factory. err := docker.Register(self, self.fsInfo) if err != nil { glog.Errorf("Docker container factory registration failed: %v.", err) } // Register the raw driver. err = raw.Register(self, self.fsInfo) if err != nil { glog.Errorf("Registration of the raw container factory failed: %v", err) }
通过上面的实例介绍,相信对golang中工厂模式的使用场景,使用方式,以及具体实现步骤,都有了一个了解,也可以把factory模式应用到自己的实际项目中了,多个driver对上统一暴露相同的接口,让项目结构更清晰。