嗯,個人代碼沒有
else
系列,一個設計模式業務真實使用的golang系列。前端
系列全集 點這git
本系列主要分享,如何在咱們的真實業務場景中使用設計模式。github
本系列文章主要採用以下結構:golang
本文主要介紹「組合模式」如何在真實業務場景中使用。後端
一個具備層級關係的對象由一系列擁有父子關係的對象經過樹形結構組成。設計模式
組合模式的優點:bash
知足以下要求的全部場景:app
Get請求獲取頁面數據的全部接口函數
前端大行組件化的當今,咱們在寫後端接口代碼的時候仍是按照業務思路一頭寫到尾嗎?咱們是否能夠思索,「後端接口業務代碼如何能夠簡單快速組件化?」,答案是確定的,這就是「組合模式」的做用。組件化
咱們利用「組合模式」的定義和前端模塊的劃分去構建後端業務代碼結構:
咱們有哪些真實業務場景能夠用「組合模式」呢?
好比咱們以「複雜的訂單結算頁面」爲例,下面是某東的訂單結算頁面:
從頁面的展現形式上,能夠看出:
關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:
按照如上某東的訂單結算頁面的示例,咱們獲得了以下的訂單結算頁面模塊組成圖:
注:模塊不必定徹底準確
責任鏈模式主要類主要包含以下特性:
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圖:
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. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。
複製代碼