代码组件 | Go设计模式实战
嗯,Go设计模式实战系列,一个设计模式业务真实使用的golang系列。
http://tigerb.cn/
前言
本系列主要分享,如何在我们的真实业务场景中使用设计模式。
本系列文章主要采用如下结构:
- 什么是「XX设计模式」?
- 什么真实业务场景可以使用「XX设计模式」?
- 怎么用「XX设计模式」?
本文主要介绍「组合模式」如何在真实业务场景中使用。
什么是「组合模式」?
一个具有层级关系的对象由一系列拥有父子关系的对象通过树形结构组成。
组合模式的优势:
- 所见即所码:你所看见的代码结构就是业务真实的层级关系,比如Ui界面你真实看到的那样。
- 高度封装:单一职责。
- 可复用:不同业务场景,相同的组件可被重复使用。
什么真实业务场景可以用「组合模式」?
满足如下要求的所有场景:
Get请求获取页面数据的所有接口
前端大行组件化的当今,我们在写后端接口代码的时候还是按照业务思路一头写到尾吗?我们是否可以思索,「后端接口业务代码如何可以简单快速组件化?」,答案是肯定的,这就是「组合模式」的作用。
我们利用「组合模式」的定义和前端模块的划分去构建后端业务代码结构:
- 前端单个模块 -> 对应后端:具体单个类 -> 封装的过程
- 前端模块父子组件 -> 对应后端:父类内部持有多个子类(非继承关系,合成复用关系) -> 父子关系的树形结构
我们有哪些真实业务场景可以用「组合模式」呢?
比如我们以“复杂的订单结算页面”为例,下面是某东的订单结算页面:
从页面的展示形式上,可以看出:
- 页面由多个模块构成,比如:
- 地址模块
- 支付方式模块
- 店铺模块
- 发票模块
- 优惠券模块
- 某豆模块
- 礼品卡模块
- 订单详细金额模块
- 单个模块可以由多个子模块构成
怎么用「组合模式」?
关于怎么用,完全可以生搬硬套我总结的使用设计模式的四个步骤:
业务梳理
按照如上某东的订单结算页面的示例,我们得到了如下的订单结算页面模块组成图:
注:模块不一定完全准确
代码建模
责任链模式主要类主要包含如下特性:
- 成员属性
ChildComponents
: 子组件列表 -> 稳定不变的
- 成员方法
Mount
: 添加一个子组件 -> 稳定不变的
Remove
: 移除一个子组件 -> 稳定不变的
Do
: 执行组件&子组件 -> 变化的
套用到订单结算页面信息接口伪代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 一个父类(抽象类): - 成员属性 + `ChildComponents`: 子组件列表 - 成员方法 + `Mount`: 实现添加一个子组件 + `Remove`: 实现移除一个子组件 + `Do`: 抽象方法
组件一,订单结算页面组件类(继承父类、看成一个大的组件): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件二,地址组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件三,支付方式组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件四,店铺组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件五,商品组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件六,优惠信息组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件七,物流组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件八,发票组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件九,优惠券组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件十,礼品卡组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件十一,订单金额详细信息组件(继承父类): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑 组件十二,售后组件(继承父类,未来扩展的组件): - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
|
但是,golang里没有的继承的概念,要复用成员属性ChildComponents
、成员方法Mount
、成员方法Remove
怎么办呢?我们使用合成复用
的特性变相达到“继承复用”的目的,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 一个接口(interface): + 抽象方法`Mount`: 添加一个子组件 + 抽象方法`Remove`: 移除一个子组件 + 抽象方法`Do`: 执行组件&子组件
一个基础结构体`BaseComponent`: - 成员属性 + `ChildComponents`: 子组件列表 - 成员方法 + 实体方法`Mount`: 添加一个子组件 + 实体方法`Remove`: 移除一个子组件 + 实体方法`ChildsDo`: 执行子组件
组件一,订单结算页面组件类: - 合成复用基础结构体`BaseComponent` - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件二,地址组件: - 合成复用基础结构体`BaseComponent` - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
组件三,支付方式组件: - 合成复用基础结构体`BaseComponent` - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
...略
组件十一,订单金额详细信息组件: - 合成复用基础结构体`BaseComponent` - 成员方法 + `Do`: 执行当前组件的逻辑,执行子组件的逻辑
|
同时得到了我们的UML图:
代码demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
| package main
import ( "fmt" "reflect" "runtime" )
type Context struct{}
type Component interface { Mount(c Component, components ...Component) error Remove(c Component) error Do(ctx *Context) error }
type BaseComponent struct { ChildComponents []Component }
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 }
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 }
func (bc *BaseComponent) Do(ctx *Context) (err error) { return }
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) { for _, childComponent := range bc.ChildComponents { if err = childComponent.Do(ctx); err != nil { return err } } return }
type CheckoutPageComponent struct { BaseComponent }
func (bc *CheckoutPageComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "订单结算页面组件...")
bc.ChildsDo(ctx)
return }
type AddressComponent struct { BaseComponent }
func (bc *AddressComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "地址组件...")
bc.ChildsDo(ctx)
return }
type PayMethodComponent struct { BaseComponent }
func (bc *PayMethodComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "支付方式组件...")
bc.ChildsDo(ctx)
return }
type StoreComponent struct { BaseComponent }
func (bc *StoreComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "店铺组件...")
bc.ChildsDo(ctx)
return }
type SkuComponent struct { BaseComponent }
func (bc *SkuComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "商品组件...")
bc.ChildsDo(ctx)
return }
type PromotionComponent struct { BaseComponent }
func (bc *PromotionComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "优惠信息组件...")
bc.ChildsDo(ctx)
return }
type ExpressComponent struct { BaseComponent }
func (bc *ExpressComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "物流组件...")
bc.ChildsDo(ctx)
return }
type AftersaleComponent struct { BaseComponent }
func (bc *AftersaleComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "售后组件...")
bc.ChildsDo(ctx)
return }
type InvoiceComponent struct { BaseComponent }
func (bc *InvoiceComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "发票组件...")
bc.ChildsDo(ctx)
return }
type CouponComponent struct { BaseComponent }
func (bc *CouponComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "优惠券组件...")
bc.ChildsDo(ctx)
return }
type GiftCardComponent struct { BaseComponent }
func (bc *GiftCardComponent) Do(ctx *Context) (err error) { fmt.Println(runFuncName(), "礼品卡组件...")
bc.ChildsDo(ctx)
return }
type OrderComponent struct { BaseComponent }
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.Do(&Context{}) }
func runFuncName() string { pc := make([]uintptr, 1) runtime.Callers(2, pc) f := runtime.FuncForPC(pc[0]) return f.Name() }
|
代码运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| [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 2 3
| 特别说明: 1. 我的代码没有`else`,只是一个在代码合理设计的情况下自然而然无限接近或者达到的结果,并不是一个硬性的目标,务必较真。 2. 本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。
|
文章列表
Go设计模式实战系列 更多文章 点击此处查看