Golang factory pattern combat writing

Work together to create and grow together! This is the 33rd day of my participation in the "Nuggets Daily New Plan·August Update Challenge", click to view the details of the event

Today, I will show you how to use Go to write the code of the factory pattern. Let's learn a practical case. This writing method is often used by the author every day, and it can effectively help everyone achieve Separation of Concerns.

Remove the motherboard

The main board is the main flow of a program. For example, we need to digest and absorb knowledge based on a learning material. We may have the following steps:

  1. prepare a notebook;
  2. open data;
  3. Read the content of the material, think and record the key points in the notebook;
  4. Do the exercises included in the material;
  5. Summarize and verify mastery.

This information can be a paper book, an e-book, or a column on a certain platform. There are many forms, but we don't care, because in the theme process, it only needs to be a material and has the ability to do so.

In other words, we convert the data into an interface, defined as follows:

type KnowledgeMaterial interface{
	GetContent() string
	GetExercises() []string
}
复制代码

It can give us the main content and give us practice questions. It is enough to satisfy these two points.

Therefore, the motherboard does not essentially care what this information is.

The extension is based on the implementation of the interface, or analogous to the adapter, it is an adapter. We can define various extensions Book, all of Ebookwhich Columnimplement this KnowledgeMaterialinterface .

When many students write code, they cannot disassemble the motherboard and do not know what their core process is. This is very important. If the [main process] cannot be disassembled, it means that when you need to implement logic for a certain entity, you directly rely on this [implementation].

For example, in our case above, there is no KnowledgeMaterialinterface , and your process becomes, open the first page of the paper book, look at the table of contents, then turn to the first chapter and start reading the text on the book. . . .

This is a scary thing, it means that once the structure changes, your code is impossible to adapt. You'll need various if else's to determine what type it is. If there is another kind of learning material called [video course], what should I do at this time?

There is no page for you to turn, and the entity you are facing becomes video content. It is not easy to adapt to it.

所以,大家一定要练习这个能力,遇到问题,思考自己的主流程是什么,拆出主板,然后明确你对业务实体的诉求是什么,能否抽象化。

是一个实现了KnowledgeMaterial 接口的任意实体就 ok?还是必须得是 Book 这个具体的结构体才 ok?

如果你需要的只是个接口,能够抽象简化,就尽量用我们今天要说的工厂模式来做,这样你的主流程心智负担会小很多,此后新增扩展成本也很小。

工厂模式流程

  1. 抽象出对实体的能力要求,变成接口;
  2. 实现工厂,支持适配器注册,支持根据类型获取对应的接口实现;
  3. 主流程只依赖接口完成;
  4. 将你的扩展,变成 adapter 适配器,实现接口所要求的的能力;
  5. 将你的适配器通过第二步里提到的方法,注册到工厂里。

这样的好处就在于,主板和扩展隔离,新增扩展的时候,只需要新增,不需要动主流程,不需要动其他扩展,避免了一大堆 if else 的写法。

代码实战

我们结合一开始提到的 KnowledgeMaterial 接口来简单示例一下。

抽象能力,定义接口

type KnowledgeMaterial interface{
	GetContent() string
	GetExercises() []string
}
复制代码

实现工厂,支持注册和获取实现

新建一个 factory.go 文件,填充如下内容:


type KnowledgeAdapterFactory struct {
	sync.RWMutex
	adapters []KnowledgeAdapter
}

var (
	knowledgeAdapterFactory = KnowledgeAdapterFactory{
		adapters: []KnowledgeAdapter{},
	}
)

// RegisterKnowledgeAdapter 注册新的知识资料适配器
func RegisterKnowledgeAdapter(adapter KnowledgeAdapter) {
	knowledgeAdapterFactory.Lock()
	knowledgeAdapterFactory.adapters = append(knowledgeAdapterFactory.adapters, adapter)
        knowledgeAdapterFactory.Unlock()

}

// GetAllKnowledgeAdapters 获取所有知识资料适配器
func GetAllKnowledgeAdapters() []KnowledgeAdapter {
	return knowledgeAdapterFactory.adapters
}
复制代码

主流程只依赖接口完成

重点关注和 adapter 相关的逻辑,其他部分省略:

func LearnKnowledge() {
	//准备好笔记本
	notes := openNotesForWrite()

	for _, adapter := range GetAllKnowledgeAdapters() {
		content := adapter.GetContent()

		// 阅读资料内容,思考并记录关键点到笔记本上
		writeNotes(content)

		// 做资料里包含的练习题
		for _, ex := range adapter.GetExercises() {
			doExecise(ex)
		}
	}

	// 归纳总结,验证掌握程度
	summary()
}
复制代码

扩展 => 适配器,实现接口

新建一个包:book,用于实现纸质书籍的适配器。在其中新建 adapter.go 文件,填充如下代码

type Adapter struct {}

func (a *Adapter) GetContent() string {
	return "xxx"
}

func (a *Adapter) GetExercises() []string {
	return []string{"xxx"}
}
复制代码

注册适配器到工厂里

这里写法其实相对灵活,很多人会选择直接在工厂定义的 factory.go 写注册逻辑,我个人不太喜欢这样。这就意味着每次新增适配器,都需要动工厂。

比较推荐直接在适配器的 init() 函数中完成注册,然后在 main 函数启动时 import 包进来,就执行了 init 函数。

这样写的好处在于当你新增一个扩展的时候,主流程和工厂都不需要动,只新增文件就好。

我们可以把上面的 adapter.go 新增一个函数即可:

type Adapter struct {}

func init() {
	RegisterKnowledgeAdapter(&Adapter{})
}

func (a *Adapter) GetContent() string {
	return "xxx"
}

func (a *Adapter) GetExercises() []string {
	return []string{"xxx"}
}

复制代码

小结

The factory mode is a very simple and easy-to-use way of writing. The point is that you should distinguish the mainboard and the expansion, and fill the adapter by registration, rather than by if else. I hope the writing method introduced today is helpful to you. There can be many variants here, and the essence is similar.

Thank you for reading, and welcome to exchange in the comment area!

Guess you like

Origin juejin.im/post/7136852697872859173