我的代碼沒有else

前言

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

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

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

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

什麼是「組合模式」?

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

組合模式的優勢:

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

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

滿足如下要求的所有場景:

Get請求獲取頁面數據的所有接口

前端大行組件化的當今,我們在寫後端接口代碼的時候還是按照業務思路一頭寫到尾嗎?我們是否可以思索,「後端接口業務代碼如何可以簡單快速組件化?」,答案是肯定的,這就是「組合模式」的作用。

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

  • 前端單個模塊 -> 對應後端:具體單個類 -> 封裝的過程
  • 前端模塊父子組件 -> 對應後端:父類內部持有多個子類(非繼承關係,合成複用關係) -> 父子關係的樹形結構

我們有哪些真實業務場景可以用「組合模式」呢?

比如我們以“複雜的訂單結算頁面”為例,下面是某東的訂單結算頁面:

我的代碼沒有else

從頁面的展示形式上,可以看出:

  • 頁面由多個模塊構成,比如: 地址模塊 支付方式模塊 店鋪模塊 發票模塊 優惠券模塊 某豆模塊 禮品卡模塊 訂單詳細金額模塊
  • 單個模塊可以由多個子模塊構成 店鋪模塊,又由如下模塊構成: 商品模塊 售後模塊 優惠模塊 物流模塊

怎麼用「組合模式」?

關於怎麼用,完全可以生搬硬套我總結的使用設計模式的四個步驟:

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

業務梳理

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

我的代碼沒有else

注:模塊不一定完全準確

代碼建模

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

  • 成員屬性 ChildComponents: 子組件列表 -> 穩定不變的
  • 成員方法 Mount: 添加一個子組件 -> 穩定不變的 Remove: 移除一個子組件 -> 穩定不變的 Do: 執行組件&子組件 -> 變化的

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

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

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

組件二,地址組件(繼承父類):

- 成員方法
\t+ `Do`: 執行當前組件的邏輯,執行子組件的邏輯

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

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

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

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

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

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

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

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

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

但是,golang裡沒有的繼承的概念,要複用成員屬性ChildComponents、成員方法Mount、成員方法Remove怎麼辦呢?我們使用合成複用的特性變相達到“繼承複用”的目的,如下:

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

一個基礎結構體`BaseComponent`:
- 成員屬性

\t+ `ChildComponents`: 子組件列表
- 成員方法
\t+ 實體方法`Mount`: 添加一個子組件
\t+ 實體方法`Remove`: 移除一個子組件
\t+ 實體方法`ChildsDo`: 執行子組件

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

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

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

...略

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

同時得到了我們的UML圖:

我的代碼沒有else

代碼demo

<code>package main

import (
\t"fmt"
\t"reflect"
\t"runtime"
)

//------------------------------------------------------------
//我的代碼沒有`else`系列
//組合模式
//@auhtor TIGERB<https>
//------------------------------------------------------------

// Context 上下文
type Context struct{}


// Component 組件接口
type Component interface {
\t// 添加一個子組件
\tMount(c Component, components ...Component) error
\t// 移除一個子組件
\tRemove(c Component) error
\t// 執行組件&子組件
\tDo(ctx *Context) error
}

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

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

// Remove 移除一個子組件
func (bc *BaseComponent) Remove(c Component) (err error) {
\tif len(bc.ChildComponents) == 0 {
\t\treturn
\t}
\tfor k, childComponent := range bc.ChildComponents {
\t\tif c == childComponent {
\t\t\tfmt.Println(runFuncName(), "移除:", reflect.TypeOf(childComponent))
\t\t\tbc.ChildComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
\t\t}
\t}
\treturn
}

// Do 執行組件&子組件

func (bc *BaseComponent) Do(ctx *Context) (err error) {
\t// do nothing
\treturn
}

// ChildsDo 執行子組件
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
\t// 執行子組件
\tfor _, childComponent := range bc.ChildComponents {
\t\tif err = childComponent.Do(ctx); err != nil {
\t\t\treturn err
\t\t}
\t}
\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

// Do 執行組件&子組件

func (bc *AddressComponent) Do(ctx *Context) (err error) {
\t// 當前組件的業務邏輯寫這
\tfmt.Println(runFuncName(), "地址組件...")

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

// Do 執行組件&子組件
func (bc *StoreComponent) Do(ctx *Context) (err error) {
\t// 當前組件的業務邏輯寫這

\tfmt.Println(runFuncName(), "店鋪組件...")

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)


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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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


\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

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

\t// 執行子組件
\tbc.ChildsDo(ctx)

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

\treturn
}

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

\t// 掛載子組件
\tstoreComponent := &StoreComponent{}
\tskuComponent := &SkuComponent{}
\tskuComponent.Mount(
\t\t&PromotionComponent{},
\t\t&AftersaleComponent{},
\t)
\tstoreComponent.Mount(
\t\tskuComponent,
\t\t&ExpressComponent{},
\t)

\t// 掛載組件
\tcheckoutPage.Mount(
\t\t&AddressComponent{},
\t\t&PayMethodComponent{},
\t\tstoreComponent,
\t\t&InvoiceComponent{},
\t\t&CouponComponent{},
\t\t&GiftCardComponent{},
\t\t&OrderComponent{},
\t)

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

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

// 獲取正在運行的函數名
func runFuncName() string {
\tpc := make([]uintptr, 1)
\truntime.Callers(2, pc)
\tf := runtime.FuncForPC(pc[0])
\treturn f.Name()
}/<https>/<code>

代碼運行結果:

<code>[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 訂單金額詳細信息組件.../<code>

結語

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

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

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

  • 責任鏈模式: 鏈表
  • 組合模式:樹
<code>特別說明: 

1. 我的代碼沒有`else`,只是一個在代碼合理設計的情況下自然而然無限接近或者達到的結果,並不是一個硬性的目標,務必較真。
2. 本系列的一些設計模式的概念可能和原概念存在差異,因為會結合實際使用,取其精華,適當改變,靈活使用。/<code>


作者:TIGERB
鏈接:https://juejin.im/post/5e8db6d46fb9a03c6e6422c0


分享到:


相關文章: