转载

Factory Pattern in Golang

具体的问题可以参考 这个 ,里面有比较详细的背景介绍。

大概就是说,在项目中的时候,常常会遇到这种情况,一个backend的接口,之后多个实际的backend的实现,一个driver的接口,多个实际类型的driver的实现,那么这种场景下的代码应该如何处理?

因为golang中没有动态加载的这种模式,就是传递一个具体的driver名称过来,接下来怎么根据这个指定的driver名称来加载上对应的实际的struct呢?

docker/distribution中的例子

最近正好有看一点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。还有一个 registercreate 的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 的接口包装了起来。

google/cadvisor项目中的 container package

这里采用的源码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对上统一暴露相同的接口,让项目结构更清晰。

原文  http://wangzhezhe.github.io/blog/2016/07/02/factory-pattern-in-golang/
正文到此结束
Loading...