代碼組件 | 個人代碼沒有else

嗯,個人代碼沒有 else系列,一個設計模式業務真實使用的golang系列。

個人代碼沒有else系列.jpg

前言

本系列主要分享,如何在咱們的真實業務場景中使用設計模式。前端

本系列文章主要採用以下結構:git

  • 什麼是「XX設計模式」?
  • 什麼真實業務場景可使用「XX設計模式」?
  • 怎麼用「XX設計模式」?

本文主要介紹「組合模式」如何在真實業務場景中使用。github

什麼是「組合模式」?

一個具備層級關係的對象由一系列擁有父子關係的對象經過樹形結構組成。

組合模式的優點:golang

  • 所見即所碼:你所看見的代碼結構就是業務真實的層級關係,好比Ui界面你真實看到的那樣。
  • 高度封裝:單一職責。
  • 可複用:不一樣業務場景,相同的組件可被重複使用。

什麼真實業務場景能夠用「組合模式」?

知足以下要求的全部場景:後端

Get請求獲取頁面數據的全部接口

前端大行組件化的當今,咱們在寫後端接口代碼的時候仍是按照業務思路一頭寫到尾嗎?咱們是否能夠思索,「後端接口業務代碼如何能夠簡單快速組件化?」,答案是確定的,這就是「組合模式」的做用。設計模式

咱們利用「組合模式」的定義和前端模塊的劃分去構建後端業務代碼結構:app

  • 前端單個模塊 -> 對應後端:具體單個類 -> 封裝的過程
  • 前端模塊父子組件 -> 對應後端:父類內部持有多個子類(非繼承關係,合成複用關係) -> 父子關係的樹形結構
咱們有哪些真實業務場景能夠用「組合模式」呢?

好比咱們以「複雜的訂單結算頁面」爲例,下面是某東的訂單結算頁面:函數

image

從頁面的展現形式上,能夠看出:組件化

  • 頁面由多個模塊構成,好比:測試

    • 地址模塊
    • 支付方式模塊
    • 店鋪模塊
    • 發票模塊
    • 優惠券模塊
    • 某豆模塊
    • 禮品卡模塊
    • 訂單詳細金額模塊
  • 單個模塊能夠由多個子模塊構成

    • 店鋪模塊,又由以下模塊構成:

      • 商品模塊
      • 售後模塊
      • 優惠模塊
      • 物流模塊

怎麼用「組合模式」?

關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:

  • 業務梳理
  • 業務流程圖
  • 代碼建模
  • 代碼demo

業務梳理

按照如上某東的訂單結算頁面的示例,咱們獲得了以下的訂單結算頁面模塊組成圖:

注:模塊不必定徹底準確

代碼建模

責任鏈模式主要類主要包含以下特性:

  • 成員屬性

    • ChildComponents: 子組件列表 -> 穩定不變的
  • 成員方法

    • Mount: 添加一個子組件 -> 穩定不變的
    • Remove: 移除一個子組件 -> 穩定不變的
    • Do: 執行組件&子組件 -> 變化的

套用到訂單結算頁面信息接口僞代碼實現以下:

一個父類(抽象類):
- 成員屬性
    + `ChildComponents`: 子組件列表
- 成員方法
    + `Mount`: 實現添加一個子組件
    + `Remove`: 實現移除一個子組件
    + `Do`: 抽象方法

組件一,訂單結算頁面組件類(繼承父類、當作一個大的組件): 
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件二,地址組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件三,支付方式組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件四,店鋪組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件五,商品組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件六,優惠信息組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件七,物流組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件八,發票組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件九,優惠券組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件十,禮品卡組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件十一,訂單金額詳細信息組件(繼承父類):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯
組件十二,售後組件(繼承父類,將來擴展的組件):
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

可是,golang裏沒有的繼承的概念,要複用成員屬性ChildComponents、成員方法Mount、成員方法Remove怎麼辦呢?咱們使用合成複用的特性變相達到「繼承複用」的目的,以下:

一個接口(interface):
+ 抽象方法`Mount`: 添加一個子組件
+ 抽象方法`Remove`: 移除一個子組件
+ 抽象方法`Do`: 執行組件&子組件

一個基礎結構體`BaseComponent`:
- 成員屬性
    + `ChildComponents`: 子組件列表
- 成員方法
    + 實體方法`Mount`: 添加一個子組件
    + 實體方法`Remove`: 移除一個子組件
    + 實體方法`ChildsDo`: 執行子組件

組件一,訂單結算頁面組件類: 
- 合成複用基礎結構體`BaseComponent` 
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件二,地址組件:
- 合成複用基礎結構體`BaseComponent` 
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

組件三,支付方式組件:
- 合成複用基礎結構體`BaseComponent` 
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

...略

組件十一,訂單金額詳細信息組件:
- 合成複用基礎結構體`BaseComponent` 
- 成員方法
    + `Do`: 執行當前組件的邏輯,執行子組件的邏輯

同時獲得了咱們的UML圖:

代碼demo

package main

import (
    "fmt"
    "reflect"
    "runtime"
)

//------------------------------------------------------------
//個人代碼沒有`else`系列
//組合模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------

// Context 上下文
type Context struct{}

// Component 組件接口
type Component interface {
    // 添加一個子組件
    Mount(c Component, components ...Component) error
    // 移除一個子組件
    Remove(c Component) error
    // 執行組件&子組件
    Do(ctx *Context) error
}

// BaseComponent 基礎組件
// 實現Add:添加一個子組件
// 實現Remove:移除一個子組件
type BaseComponent struct {
    // 子組件列表
    ChildComponents []Component
}

// Mount 掛載一個子組件
func (bc *BaseComponent) Mount(c Component, components ...Component) (err error) {
    bc.ChildComponents = append(bc.ChildComponents, c)
    if len(components) == 0 {
        return
    }
    bc.ChildComponents = append(bc.ChildComponents, components...)
    return
}

// Remove 移除一個子組件
func (bc *BaseComponent) Remove(c Component) (err error) {
    if len(bc.ChildComponents) == 0 {
        return
    }
    for k, childComponent := range bc.ChildComponents {
        if c == childComponent {
            fmt.Println(runFuncName(), "移除:", reflect.TypeOf(childComponent))
            bc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
        }
    }
    return
}

// Do 執行組件&子組件
func (bc *BaseComponent) Do(ctx *Context) (err error) {
    // do nothing
    return
}

// ChildsDo 執行子組件
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
    // 執行子組件
    for _, childComponent := range bc.ChildComponents {
        if err = childComponent.Do(ctx); err != nil {
            return err
        }
    }
    return
}

// CheckoutPageComponent 訂單結算頁面組件
type CheckoutPageComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *CheckoutPageComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "訂單結算頁面組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// AddressComponent 地址組件
type AddressComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *AddressComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "地址組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// PayMethodComponent 支付方式組件
type PayMethodComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *PayMethodComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "支付方式組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// StoreComponent 店鋪組件
type StoreComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *StoreComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "店鋪組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// SkuComponent 商品組件
type SkuComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *SkuComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "商品組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// PromotionComponent 優惠信息組件
type PromotionComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *PromotionComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "優惠信息組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// ExpressComponent 物流組件
type ExpressComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *ExpressComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "物流組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// AftersaleComponent 售後組件
type AftersaleComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *AftersaleComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "售後組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// InvoiceComponent 發票組件
type InvoiceComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *InvoiceComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "發票組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// CouponComponent 優惠券組件
type CouponComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *CouponComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "優惠券組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// GiftCardComponent 禮品卡組件
type GiftCardComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *GiftCardComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "禮品卡組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

// OrderComponent 訂單金額詳細信息組件
type OrderComponent struct {
    // 合成複用基礎組件
    BaseComponent
}

// Do 執行組件&子組件
func (bc *OrderComponent) Do(ctx *Context) (err error) {
    // 當前組件的業務邏輯寫這
    fmt.Println(runFuncName(), "訂單金額詳細信息組件...")

    // 執行子組件
    bc.ChildsDo(ctx)

    // 當前組件的業務邏輯寫這

    return
}

func main() {
    // 初始化訂單結算頁面 這個大組件
    checkoutPage := &CheckoutPageComponent{}

    // 掛載子組件
    storeComponent := &StoreComponent{}
    skuComponent := &SkuComponent{}
    skuComponent.Mount(
        &PromotionComponent{},
        &AftersaleComponent{},
    )
    storeComponent.Mount(
        skuComponent,
        &ExpressComponent{},
    )

    // 掛載組件
    checkoutPage.Mount(
        &AddressComponent{},
        &PayMethodComponent{},
        storeComponent,
        &InvoiceComponent{},
        &CouponComponent{},
        &GiftCardComponent{},
        &OrderComponent{},
    )

    // 移除組件測試
    // checkoutPage.Remove(storeComponent)

    // 開始構建頁面組件數據
    checkoutPage.Do(&Context{})
}

// 獲取正在運行的函數名
func runFuncName() string {
    pc := make([]uintptr, 1)
    runtime.Callers(2, pc)
    f := runtime.FuncForPC(pc[0])
    return f.Name()
}

代碼運行結果:

[Running] go run "../easy-tips/go/src/patterns/composite/composite.go"
main.(*CheckoutPageComponent).Do 訂單結算頁面組件...
main.(*AddressComponent).Do 地址組件...
main.(*PayMethodComponent).Do 支付方式組件...
main.(*StoreComponent).Do 店鋪組件...
main.(*SkuComponent).Do 商品組件...
main.(*PromotionComponent).Do 優惠信息組件...
main.(*AftersaleComponent).Do 售後組件...
main.(*ExpressComponent).Do 物流組件...
main.(*InvoiceComponent).Do 發票組件...
main.(*CouponComponent).Do 優惠券組件...
main.(*GiftCardComponent).Do 禮品卡組件...
main.(*OrderComponent).Do 訂單金額詳細信息組件...

結語

最後總結下,「組合模式」抽象過程的核心是:

  • 按模塊劃分:業務邏輯歸類,收斂的過程。
  • 父子關係(樹):把收斂以後的業務對象按父子關係綁定,依次被執行。

與「責任鏈模式」的區別:

  • 責任鏈模式: 鏈表
  • 組合模式:樹
特別說明:
1. 個人代碼沒有`else`,只是一個在代碼合理設計的狀況下天然而然無限接近或者達到的結果,並非一個硬性的目標,務必較真。
2. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。
個人代碼沒有else系列 更多文章 點擊此處查看

3911642037-d2bb08d8702e7c91_articlex.jpg

相關文章
相關標籤/搜索