背景

在开发过程中你是否有遇到过这样的苦恼?产品发来一个需要,没做过,然而看完需要感觉应该解决起来很简略,而后找到对应的业务代码,发现代码像打乱的毛线一样理不分明,各种逻辑嵌套,各种非凡判断解决,想要拓展保护个内容却无从下手,一边看着代码,一边用手拨动着本就为数不多的秀发,而后口吐芳香 。

有没发现一个问题,为什么业务不简单,然而随着产品迭代,通过一直拓展和保护,缓缓的代码就越做越乱,你能够说产品想法天马星空,人员流动大,多人参加缓缓的就被做乱了,这可能是个不错的借口,然而其中实质的问题还是后期思考的太少,没有进行正当的形象设计,没有去前瞻性的去预埋一些将来可拓展性的内容,所以最终导致了起初的场面。

常常听到有教训的开发者说开发前多思考,不要一拿到需要就习惯性的一顿操作,反手就定义一个function依据需要逻辑一条龙写到底。

所以面对绝对简单的需要咱们须要进行形象思考,尽可能做到设计进去的货色是解决一类问题,而不是单单解决以后问题,而后在代码实现上也要面向形象开发,这样能力做到真正的高质量代码,可维护性和可拓展性高,能力积淀出可复用,健壮性强的零碎。

那么咱们要如何去形象呢?面对需要的抽象思维这个须要平时多锤炼,拿到需要多想多思考,不要急于求成,次要围绕着这几大因素:可维护性、可拓展性、可复用性,安全性去设计解决方案,至于代码上的形象就能够应用上面的形式。

不卖关子了,是时候请出明天的配角:《设计模式》,简略的说设计模式就是开发者们的教训积淀,通过学习设计模式并在业务开发过程中加以应用,能够让代码的实现更容易拓展和保护,进步整体代码品质,也能够作为开发之间沟通的专业术语,提到某个模式,能够马上get到代码设计,缩小沟通的老本。

这里就不一一介绍23种设计模式和设计模式的6个准则,能够google回顾下
举荐:学习设计模式地址

上面就将联合以后我的项目的bad case,手把手的应用设计模式进行重构,其中会用到多种设计模式的应用,并且体现了设计模式的中的几个准则,做好筹备,发车了。

举例

需要背景概要:

APP首页性能,用模块化的形式去治理配置,后盾能够配置模块标识和模块排序,展现条件等,首页API接口获取以后用户的模块列表,并结构模块数据展现。

API Response 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

伪代码,疏忽掉一些不重要的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    }

看完这个代码,是否有一种要坏起来的滋味,随着模块一直减少,case会越来越多,而且每个case外面又有一些针对版本、针对AB、一些非凡解决等,让代码变得又臭又长,越来越难去拓展和保护,并且每次保护或者拓展都可能在GetHomeData() 办法里在一直往里面添枝加叶,不小心就会对整个接口产生影响。

那么咱们要如何去重构呢,这就要形象起来,这个业务自身就曾经有模块相干形象设计,这里就不进行调整,次要是针对代码上的形象,联合设计模式进行革新。

以下就是重构的过程。

刚开始的时候,看到这种case 判断,而后做模块数据的聚合,我第一反馈是,是否能够应用工厂模式,定义一个 interface,每个模块定义一个struct 实现接口ExportData() 办法,通过工厂办法去依据模块标识创建对象,而后调用导出数据办法进行数据上的聚合 。

然而在评估的过程中,发现有些模块数据里又聚合了多个不同业务知识内容的数据,单纯的工厂模式又不太适合,最初决定应用组合模式,结构型设计模式,能够将对象进行组合,实现一个相似层级对象关系,如:

# 首页模块home    - my_baby    - weight    - early_edu    - today_knowledge        - tips        - life_video    - weather    - ... ...

这里我从新定义了下名词,后盾配置的是模块,在代码实现上我把每个模块里展现的数据定义成 组件,组件又能够分成 繁多组件 和 复合组件,复合组件就是应用了多个繁多组件组成。

UML结构图:

Refactor After Code:

定义组件接口 IElement :

// IElement 组件接口type IElement interface {    // Add 增加组件,繁多组件,能够不必实现具体方法内容    Add(compUni string, compo IElement)    // ExportData 输入组件数据    ExportData(parameter map[string]interface{}) (interface{}, error)}

定义组件类型枚举

// EElement 组件类型type EElement stringconst (    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)}

繁多组件的实现

// 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}// ... ...

复合组件:

// 今日常识,组合多个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}

因为有些常识数据的内容曾经有相干实现,并且能够结构对象进行调用,咱们须要做的是依据组件需要适配成组件须要的数据结构进行输入,这里又引入了适配器模式,能够应用适配器模式,将其适配成以后组件须要的数据结构输入。

// 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}

对象的创立须要进行对立治理,便于后续的拓展和替换,这里引入工厂模式,封装组件的对象创立,通过工厂办法去创立组件对象。

// 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    }}

辣妈首页模块数据聚合:

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}

革新相干内容,over ~

通过革新,后续再拓展或者保护首页模块数据的时候,根本不须要动到获取数据的办法:GetHomeModules() ,拓展的时候只须要去拓展一个组件枚举类型,而后定义组件 struct 实现 组件接口 IElement 办法,在组件工厂 ElemFactory 中拓展对象创立,保护组件的时候也只须要对ExportData() 批改。

这次的重构计划中体现了设计模式的几个准则,咱们形象了组件接口,针对接口编程,不针对实现编程,满足接口隔离准则,并且对批改敞开,对拓展凋谢,满足了开闭准则。

总结:

最初,为了缩小反复的代码开发,防止做添枝加叶的事件,为了我的项目的可维护性,可拓展性,也防止成为后人口吐芳香的对象,咱们须要设计起来,实现能够应答变动,有弹性的零碎。