单例泛型工厂

背景

我们需要一个工厂,这个工厂里生产针对各种不同领域的策略。比如说节点的相关操作,创建节点,节点迁移就是一个领域,实例的相关操作,创建库,创建表也是一个领域。
而且这个工厂会被很多地方调用,所以我们希望把它做成一个单例类。

遇到的问题

当我想要支持节点领域的时候,我就需要写节点领域工厂的代码,里面通常有两个方法,一个是产品注册到工厂,一个是从工厂获取产品,示意代码如下。

// INodeFactory 策略工厂
type INodeFactory interface {
	GetStrategy(key string) INodeProduct
	RegisterStrategy(key string, product INodeProduct)
}

var (
	localFactory INodeFactory
	onceFactory  sync.Once
)

// NodeFactory 获取策略工厂
func NodeFactory() INodeFactory {
	onceFactory.Do(func() {
		localFactory = &nodeFactory{
			localNodeMap: map[string]INodeProduct{},
		}
	})
	return localFactory
}

type nodeFactory struct {
	localNodeMap map[string]INodeProduct
}

// GetStrategy 获取策略
func (n *nodeFactory) GetStrategy(key string) INodeProduct {
	return nil
}

// RegisterStrategy 注册策略
func (n *nodeFactory) RegisterStrategy(key string, product INodeProduct) {
}

这么写是没问题的,但是当我们领域非常多的时候,问题就出现了。我们需要为每一个领域书写工厂代码,而且每个领域的工厂其实都长的一样,都只需要两个方法,注册产品和获取产品,这样代码就会显得非常的冗余。

思考

想着既然代码都一样,那能不能合成一个代码呢。

一个大工厂

第一个版本想着能不能把所有领域的工厂都合并起来,弄一个大工厂,一个工厂里面负责生产所有的产品。

// IFactory 策略工厂
type IFactory interface {
	GetStrategy(domain, key string) INodeProduct
	RegisterStrategy(domain, key string, product INodeProduct)
}

var (
	localFactory IFactory
	onceFactory  sync.Once
)

// Factory 获取策略工厂
func Factory() IFactory {
	onceFactory.Do(func() {
		localFactory = &factory{
			localNodeMap:     map[string]map[string]INodeProduct{},
			localInstanceMap: map[string]map[string]IInstanceProduct{},
		}
	})
	return localFactory
}

type factory struct {
	localNodeMap     map[string]map[string]INodeProduct
	localInstanceMap map[string]map[string]IInstanceProduct
}

// GetStrategy 获取策略
func (n *factory) GetStrategy(domain, key string) INodeProduct {
	return nil
}

// RegisterStrategy 注册策略
func (n *factory) RegisterStrategy(domain, key string, product INodeProduct) {
}

这么写是可以满足需求的,采用一个大工厂也解决了刚刚的问题,这样每次新加一个领域的时候只需要添加一个 localXXXMap 就可以了。
但是这种写法会比较奇怪,因为明明是不同的产品,为什么要放在同一个工厂里面生产呢。而且这个工厂也会随着领域的增加,代码量逐步增长。
有没有什么方法可以解决这个问题呢。

一个大工厂-改版

既然不想工厂随着领域的增加而代码膨胀,那能不能将所有的领域Map都合成一个呢。答案是可以。

type factory struct {
	localMap map[string]map[string]interface{}
}

只需要将map里面的value由具体类型换成interface{}不就可以了,这样就可以使用一个map存下所有的产品。
但是这样只是将代码复杂度由工厂内转移到了使用方,因为存储的是interface{},那么GetStrategy()方法就会变成下面这样

// GetStrategy 获取策略
func (n *factory) GetStrategy(domain, key string) interface{} {
	return nil
}

返回值变为了interface{},这样就需要调用方在自己的方法里面做强制类型转换,并判断是否转换成功。
这种方式虽然工厂代码不会膨胀,但是写起来每个调用的地方都需要转换+判断一次,不够优雅。

泛型工厂

考虑到 golang 在 1.18 的版本中已经实现了泛型技术,想着能不能使用泛型技术来让我们工厂的GetStrategy()方法返回具体的类型,这样就不需要在外面进行多余的判断。
查阅过资料过后发现golang只支持泛型函数,并不支持泛型方法。也就是说这种是不支持的

type A struct {
}

// 不支持泛型方法
func (receiver A) Add[T int | float32 | float64](a T, b T) T {
    return a + b
}

但是因为receiver支持泛型, 所以如果想在方法中使用泛型的话,目前唯一的办法就是曲线救国,迂回地通过receiver使用类型形参

type A[T int | float32 | float64] struct {
}

// 方法可以使用类型定义中的形参 T 
func (receiver A[T]) Add(a T, b T) T {
    return a + b
}

// 用法:
var a A[int]
a.Add(1, 2)

var aa A[float32]
aa.Add(1.0, 2.0)

那我们的工厂就可以写成下面这样

type Factory[T any] struct {
	localMap map[string]map[string]T
}

// GetStrategy 获取策略
func (n *Factory[T]) GetStrategy(domain, key string) T {
	var t T
	return t
}

// RegisterStrategy 注册策略
func (n *Factory[T]) RegisterStrategy(domain, key string, product T) {
}

看起来很美好,可以将多个产品通过泛型技术用同一份代码实现,在外面使用的时候也不需要进行类型转换。
但是这种实现方案与我们的需求不符,我们需要的是单例工厂,每次被调用的时候都拿到同一个工厂,如果使用泛型技术的话是没办法使用 sync.Once来明确每一个具体的泛型工厂的。

泛型工厂-改进

在搜素的时候发现rakyll写了一篇[文章](Generics facilitators in Go · rakyll.org),里面写了她遇到的问题及解决方案。里面提到的思路也比较适合我们的场景。
解决方案简单来说就是使用一个泛型的查询器将工厂包装起来,保证工厂的唯一性。

// Querier 查询器
type Querier[T any] struct {
	factory IFactory
}

func NewQuerier[T any]() *Querier[T] {
	return &Querier[T]{
		factory: GetFactory(),
	}
}

func (q *Querier[T]) GetStrategy(domain, key string) T {
	strategy := q.factory.GetStrategy(domain, key)
	return strategy.(T)
}

// IFactory 策略工厂
type IFactory interface {
	GetStrategy(domain, key string) interface{}
	RegisterStrategy(domain, key string, product interface{})
}

var (
	localFactory IFactory
	onceFactory  sync.Once
)

// GetFactory 获取策略工厂
func GetFactory() IFactory {
	onceFactory.Do(func() {
		localFactory = &factory{
			localMap: map[string]map[string]interface{}{},
		}
	})
	return localFactory
}

type factory struct {
	localMap map[string]map[string]interface{}
}

// GetStrategy 获取策略
func (n *factory) GetStrategy(domain, key string) interface{} {
	return nil
}

// RegisterStrategy 注册策略
func (n *factory) RegisterStrategy(domain, key string, product interface{}) {
}

这样就由工厂保证了自身的唯一性,查询器可以被创建很多个,但是使用的都是同一个工厂,保证了单例工厂,也不需要每次使用的时候进行强制类型转换了。

总结

没有什么事是增加一层解决不了了,如果有,那就再增加一层。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值