likes
comments
collection
share

Go实践|使用工厂模式和策略模式获取数据源

作者站长头像
站长
· 阅读数 58

记录一次自己在处理获取不同数据源的优化过程!

工厂模式 + 策略模式

现状分析

需求内容是获取多个不同项目的数据源,每个数据源的获取方式都不相同。例如,数据源1获取的方式是通过直接请求数据库的方式获取,数据源2获取的方式通过接口的方式获取。最初没有使用任何设计直接构建对应的函数,然后每次使用的时候直接调用对应的函数。

// thirdPart.go 文件
func GetSource1() {
	// do something
}

func GetSource2() {
	// do something
}

// 使用
func main() {
	result1 := GetSource1()
	result2 := GetSource2();
}

直接使用的函数的函数也是可以的,只是没有设计,在后续的扩展时候就会发现很多问题:

  • 对象没有归类,即没有封装,导致代码当前 package 会看起来像是一组随机功能
  • 需要对对象进行继承、组合时无法实现,可能导致你需要不断增加函数来满足自己的功能
  • 对象是具有属性的,如果有些行为时连续的操作,如果不断增加参数进行传递值,增加函数来满足功能就会显的臃肿。使用封装绑定 struct,并对 struct设置一定的属性,后续在一些连续操作时可以保持实例的属性值。即将数据和行为进行封装。

使用工厂模式

对于上面数据源可以绑定每个数据源结构体,然后再使用工厂模式,就能实现不同的场景,创建不同的数据源是对象。

// 定义数据源获取接口,
type IDataSource interface {
    FetchData()([]byte, error)
}

// 数据源实体1
type Source1 struct{}
func (s *Source1)FetchData()([]byte, error){
    // do somnething
    return result,err
}

// 数据源实体2
type Source2 struct{}
func (s *Source1)FetchData()([]byte, error){
    // do somnething
    return result,err
}

// 工厂类
type Factory struct{}
func (F factory)NewFactory(key string) *IDataSource {
	switch key {
        case "source1":
         return &Source1{}
        case "source2":
         return &Source2{}
    }
}

// main.go
func main(){
    s := NewFactory("source1")
    s.FetchData()
}

可以从上面简单的代码例子可以看到:

  • IDataSource接口:定义了数据源的通用获取数据方法。主要是提供抽象层,统一了不同的数据源对象;
  • Source1和Source2结构体:实现了IDataSource接口,代表不同的数据源;
  • Factory结构体:工厂类,根据key创建不同的数据源对象。作用就是封装费了对象的创建细节;

但是具体的使用还是要根据自己的业务场景进行扩展,比如:数据源实体是复杂的。

  • 对于创建实体的过程是复杂的情况下,可以使用工厂方法进行创建,否则就会导致 NewFactory(key string) 函数过于臃肿,这也是区分使用工厂模式还是工厂方法的标准
  • 将工厂类抽象化,客户端就只依赖工厂接口,提高解耦和扩展性

不同数据源参数

由于每个数据源 FetchData 时它们的参数都是不一样的。可以看上面源码,将参数都转换为 []byte 类型。然后在具体的实现中转为其对应的类型数据。

type SourceParams struct {
	Start  string
	UserId string
}

type Source1 struct {
}

func (*Source1) FetchData(params interface{}) ([]byte, error) {
	p := params.(SourceParams)
	// do something ...
}

相同数据源不同获取方式

当需求升级,对于相同的数据源但是可能参数不一样了,或者是提供新版本的获取方式但又要保持第一版本的方式,这个时候怎么扩展。

第一版本:构建新的数据源Struct 当做新的数据源,比如 SourceV1。

type Factory struct{}
func (F factory)NewFactory(key string) *IDataSource {
	switch key {
        case "source1":
         return &Source1{}
        case "source1V1":
         return &source1V1{}
        case "source2":
         return &Source2{}
    }
}

第二版本:使用策略模式

type FetchStrategy interface{
    Fetch()([]byte,error)
}
// 获取数据方式1
type FetchV1 struct{}
func (s *FetchV1) Fetch() {
 // do something ...
}

// 获取数据方式2
type FetchV2 struct{}
func (s *FetchV2) Fetch() {
 // do something ...
}

type Source1 struct {
     strategy FetchStrategy // 数据源增加strategy 属性
}

// 获取时就可以根据对应策略获取数据
func (s *Source1) FetchData(params interface{}) ([]byte, error) {
	p := params.(SourceParams)
	// do something ...
    s.strategy.Fetch()
}

// 外层的工厂模式中返回对应的实体时可以增加对应策略key参数
func (F factory)NewFactory(key string, strategy FetchStrategy) *IDataSource {
	switch key {
        case "source1":
         return &Source1{strategy:strategy}
        case "source2":
         return &Source2{}
    }
}

从上面的源码可以看到,在Souce中增加strategy 属性,在工厂中创建DataSource时,传入不同的策略对象到strategy字段中,这样就可以动态改变所使用的策略。在DataSource的FetchData方法内,它只需要调用strategy.Fetch()来触发对应的策略行为,而不需要关心具体是哪种策略。

使用策略模式,可以灵活的切换不同的算法,后续有不同的获取的方式,只需要增加策略即可。当然使用策略模式应该还需要考虑下是不是需要使用。

最后

详细的使用的主要还是要根据自己的实际业务场景进行细节的调整,没有一层不变的源码使用,只有不断调整适合自己才是正确的。

转载自:https://juejin.cn/post/7268607295017844773
评论
请登录