嗯,个人代码没有
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. 本系列的一些设计模式的概念可能和原概念存在差别,由于会结合实际使用,取其精华,适当改变,灵活使用。
复制代码