background
Have you ever encountered such troubles during the development process? The product sent a request, but I haven't done it, but after reading the request, I feel that it should be very simple to handle, and then I find the corresponding business code, and I find that the code is as unclear as a messed up yarn, with various logic nesting, and various special judgments. Processing, want to expand and maintain a content but can't start, while looking at the code, while playing with the few hair, and then spit fragrance.
Have you found a problem, why the business is not complicated, but with the product iteration, after continuous expansion and maintenance, the code gradually becomes more and more chaotic, you can say that the product ideas are wild, the flow of personnel is large, and the participation of many people is slow. This may be a good excuse, but the essential problem is that there is too little thought in the early stage, there is no reasonable abstract design, and there is no forward-looking to bury some future expandable content, so Eventually led to the later situation.
I often hear experienced developers say that they should think more before developing, don't habitually operate as soon as they get the requirements, and then define a function backhand and write them down to the end according to the logic of the requirements.
Therefore, in the face of relatively complex needs, we need to think abstractly, and try to design something that solves a class of problems, rather than just solving the current problem, and then the code implementation should also be oriented towards abstract development, so as to achieve real The high-quality code with high maintainability and scalability can precipitate a reusable and robust system.
So how do we go about abstracting? Abstract thinking in the face of needs requires more exercise at ordinary times, thinks and thinks more when you get the needs, and don’t rush for success. It mainly revolves around these major elements: maintainability, scalability, reusability, and safety to design. The solution, as for the abstraction on the code, can use the following method.
It's time to invite today's protagonist: "Design Patterns". Simply put, design patterns are the experience of developers. By learning design patterns and using them in the business development process, the implementation of code can be easier. Expansion and maintenance can improve the overall code quality. It can also be used as a professional term for communication between developers. When a certain mode is mentioned, you can immediately get the code design and reduce the cost of communication.
I will not introduce 23 design patterns and 6 principles of design patterns one by one here, you can review them on google
Recommended: Learning Design Patterns Address
The following will be combined with the bad case of the current project, the use of design patterns will be refactored, which will use the use of a variety of design patterns, and reflect several principles of design patterns, get ready, and start.
Example
Summary of Requirements Background:
The APP homepage function manages configuration in a modular way. The background can configure module identification, module sorting, display conditions, etc. The homepage API interface obtains the current user's module list and constructs the module data display.
API Response Data
Fake response data, ignoring unimportant or duplicate data
{
"code": 0,
"data": {
"tools": {
// -- 模块信息 --
"id": 744,
"icon": "",
"name": "",
"sub_title": "",
"module": "lm_tools",
"sort": 1,
"is_lock": true,
"is_show": true,
"more_text": "",
"more_uri": "xxx:///tools/more",
"list": [
// -- 模块展示数据 --
]
},
"my_baby": {
// ... ...
},
"knowledge_parenting": {
// ... ...
},
"early_due": {
// ... ...
},
// ... ...
"message": ""
}
Before Code
Pseudo code, ignore some unimportant code
func (hm *HomeModule) GetHomeData() map[string]interface{} {
result := make(map[string]interface{})
// ... ...
// 获取模块列表
module := lm.GetHomeSortData()
// ... ...
// 构造每个模块的数据
for _, module := range moduleList {
// ... ...
switch module.Module {
case "my_baby":
// ... ...
result["my_baby"] = data
case "lm_tools":
// ... ...
result["lm_tools"] = data
case "weight":
// ... ...
result["weight"] = data
case "diagnose":
result["diagnose"] = data
case "weather":
// ... ...
result["weather"] = data
case "early_edu":
// ... ...
result["early_edu"] = data
case "today_knowledge":
// ... ...
data["tips"]=list
// ... ...
data["life_video"]=lifeVideo
// ... ...
result["today_knowledge"] = data
default:
result[module.Module] = module
}
// ... ...
return result
}
After reading this code, is there a smell that it is going to be broken? As the modules continue to increase, there will be more and more cases, and each case has some version-specific, AB-specific, some special processing, etc., so that the code becomes Smelly and long, it is more and more difficult to expand and maintain, and every maintenance or expansion may GetHomeData()
method, which will affect the entire interface if you are not careful.
So how do we refactor? This requires abstraction. The business itself already has a module-related abstract design, so we will not adjust it here. It is mainly aimed at the abstraction of the code, combined with the design pattern.
The following is the refactoring process.
At the beginning, when I saw this case judgment, and then did the aggregation of module data, my first reaction was whether I could use the factory pattern to define a interface
, and each module defined a struct
implement the interface ExportData()
method, through the factory method To create an object based on the module identifier, and then call the export data method to aggregate on the data.
However, in the process of evaluation, it was found that some module data aggregated multiple data of different business knowledge content, and the simple factory mode was not suitable. Finally, it was decided to use the combination mode and structural design mode, which can combine objects. Implement a similar hierarchical object relationship, such as:
# 首页模块
home
- my_baby
- weight
- early_edu
- today_knowledge
- tips
- life_video
- weather
- ... ...
Here I redefine the next term. The background configuration is the module. In the code implementation, I define the data displayed in each module as a component, and the component can be divided into a single component and a composite component. The composite component uses multiple single components. composition.
UML structure diagram:
Refactor After Code:
Define the component interface IElement:
// IElement 组件接口
type IElement interface {
// Add 添加组件,单一组件,可以不用实现具体方法内容
Add(compUni string, compo IElement)
// ExportData 输出组件数据
ExportData(parameter map[string]interface{}) (interface{}, error)
}
Define component type enumeration
// EElement 组件类型
type EElement string
const (
EElementTips EElement = "tips" // 贴士
EElementLifeVideo EElement = "life_video" // 生命一千天
EElementEarlyEdu EElement = "early_edu" // 早教
EElementBaby EElement = "baby" // 宝宝
ECompositeTodayKnowledge EElement = "today_knowledge" // 今日知识
// ....
)
func (ec EElement) ToStr() string {
return string(ec)
}
Implementation of a single component
// ElemTips 贴士组件
type ElemTips struct {
}
func NewCompoTips() *ElementTips {
return &ElementTips{}
}
func (c *ElementTips) Add(compoUni string, comp IElement) {
}
func (c ElementTips) ExportData(parameter map[string]interface{}) (interface{}, error) {
tips := []map[string]interface{}{
{
"id": 1,
"title": "贴士1",
},
{
"id": 2,
"title": "贴士2",
},
{
"id": 3,
"title": "贴士3",
},
{
"id": 4,
"title": "贴士4",
},
}
return tips, nil
}
// ElemLifeVideo 生命一千天组件
type ElemLifeVideo struct {
}
func NewCompoLifeVideo() *ElementLifeVideo {
return &ElementLifeVideo{}
}
func (c ElementLifeVideo) Add(compoUni string, comp IElement) {
}
func (c ElementLifeVideo) ExportData(parameter map[string]interface{}) (interface{}, error) {
lifeVideos := []map[string]interface{}{
{
"id": 1,
"title": "生命一千天1",
},
{
"id": 2,
"title": "生命一千天2",
},
{
"id": 3,
"title": "生命一千天3",
},
{
"id": 4,
"title": "生命一千天4",
},
}
return lifeVideos, nil
}
// ... ...
Composite Components:
// 今日知识,组合多个dan'yi组件
type ElemTodayKnowledge struct {
Composite map[string]IElement
}
func NewCompoTodayKnowledge() *ElemTodayKnowledge {
factory := NewElementFactory()
c := new(ElemTodayKnowledge)
c.Add(EElementTips.ToStr(), factory.CreateElement(EElementTips.ToStr()))
c.Add(EElementEarlyEdu.ToStr(), factory.CreateElement(EElementEarlyEdu.ToStr()))
return c
}
func (c *ElemTodayKnowledge) Add(compoUni string, comp IElement) {
if c.Composite == nil {
c.Composite = map[string]IElement{}
}
c.Composite[compoUni] = comp
}
func (c ElemTodayKnowledge) ExportData(parameter map[string]interface{}) (interface{}, error) {
data := map[string]interface{}{}
for uni, compo := range c.Composite {
data[uni], _ = compo.ExportData(parameter)
}
return data, nil
}
Because the content of some knowledge data has been implemented, and the object can be constructed for calling, what we need to do is to adapt it to the data structure required by the component for output, and the adapter mode is introduced here. You can use the adapter mode to convert the It is adapted to the data structure output required by the current component.
// ElemEarlyDduAdapter 早教组件 - 适配
type ElemEarlyDduAdapter struct {
edu earlyEdu.ThemeManager
}
func NewElementLifeVideoAdapter(edu earlyEdu.ThemeManager) *ElemEarlyDduAdapter {
return &ElemEarlyDduAdapter{edu: edu}
}
func (c ElemEarlyDduAdapter) Add(compoUni string, comp IElement) {
}
func (c ElemEarlyDduAdapter) ExportData(parameter map[string]interface{}) (interface{}, error) {
age, ok := parameter["age"].(uint32)
if !ok {
return nil, errors.New("缺少age")
}
birthday, ok := parameter["birthday"].(string)
if !ok {
return nil, errors.New("缺少birthday")
}
list := c.edu.GetList(age, birthday)
return list, nil
}
The creation of objects needs to be managed in a unified manner to facilitate subsequent expansion and replacement. Here, the factory mode is introduced to encapsulate the object creation of components, and create component objects through the factory method.
// ElemFactory 组件工厂
type ElemFactory struct {
}
func NewElementFactory() *ElemFactory {
return &ElemFactory{}
}
// CreateElement 内容组件对象工厂
func (e ElemFactory) CreateElement(compType string) IElement {
switch compType {
case EElementBaby.ToStr():
return NewCompoBaby()
case EElementEarlyEdu.ToStr():
return NewElementLifeVideoAdapter(earlyEdu.ThemeManager{})
case EElementLifeVideo.ToStr():
return NewCompoLifeVideo()
case EElementTips.ToStr():
return NewCompoTips()
case ECompositeTodayKnowledge.ToStr():
return NewCompoTodayKnowledge()
default:
return nil
}
}
Hot Mom Homepage Module Data Aggregation:
type HomeModule struct {
GCtx *gin.Context
}
func NewHomeModule(ctx *gin.Context) *HomeModule {
// 构建模块对象
lh := &HomeModule{
GCtx: ctx,
}
return lh
}
func (lh HomeModule) GetHomeModules() interface{} {
// 请request context 上文获取请求参数
parameter := map[string]interface{}{
"baby_id": 22000025,
"birthday": "2021-12-11",
"age": uint32(10),
// ... ...
}
// 从db获取模块列表
compos := []string{
"early_edu",
"baby",
"tips",
"today_knowledge",
}
// 组装组件
elements := map[string]element.IElement{}
elementFactory := element.NewElementFactory()
for _, compoUni := range compos {
comp := elementFactory.CreateElement(compoUni)
if comp == nil {
continue
}
elements[compoUni] = comp
}
// 聚合数据
data := map[string]interface{}{}
for uni, compo := range elements {
data[uni], _ = compo.ExportData(parameter)
}
return data
}
Renovation related content, over ~
After the transformation, when expanding or maintaining the data of the home page module, there is basically no need to move the method to obtain the data: GetHomeModules()
, when expanding, you only need to expand a component enumeration type, and then define the component struct
implement the component interface IElement
method. Component factory ElemFactory
expands the object creation, and only needs to modify ExportData()
This refactoring plan reflects several principles of the design pattern. We abstract the component interface, target the interface programming, not the implementation programming, satisfy the principle of interface isolation, and close the modification and open the extension, which satisfies the open-closed principle. .
Summarize:
Finally, in order to reduce repetitive code development, avoid doing things that add oil and vinegar, for the maintainability and scalability of the project, and to avoid becoming the object of future generations, we need to design and implement a system that can cope with changes and be flexible. .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。