Ах, Go шаблоны проектирования боевой серии, шаблон проектирования реального использования серии Golang бизнеса.
предисловие
Эта серия в основном рассказывает о том, как использовать шаблоны проектирования в наших реальных бизнес-сценариях.
Эта серия статей в основном имеет следующую структуру:
- Что такое «Шаблон проектирования XX»?
- В каких реальных бизнес-сценариях можно использовать «шаблон проектирования XX»?
- Как использовать «Шаблон дизайна XX»?
В этой статье в основном входят «комбинированный паттерн» в сочетании с присущими функциями параллелизма языка GO, могут использоваться в реальных бизнес-сценариях.
предыдущая статья«Компоненты кода | Go Design Patterns в действии»Введено понятие «составной паттерн» и его использование в бизнесе. Сегодня мы обновляем «режим композиции» до «режим параллельной композиции» в сочетании с присущими языку Go функциями параллелизма.
Давайте кратко рассмотрим знание «комбинированного режима», вы можете проверить предыдущую статью для деталей.«Компоненты кода | Go Design Patterns в действии»
Что такое «одновременный шаблон композиции»?
Концепция комбинированного режима:
Объект с иерархической связью состоит из ряда объектов с родительско-дочерней связью через древовидную структуру.
Концепция параллельного шаблона композиции:
Объект с иерархическими отношениями состоит из ряда объектов с отношениями родитель-потомок через древовидную структуру, а дочерние объекты могут выполняться последовательно или одновременно.
Преимущества параллельного шаблона композиции:
- Первоначально серийные услуги (заблокированные детали, такие как сеть IO и т. Д.) Могут выполняться одновременно, воспользовавшись многоядерными для повышения производительности.
В каких реальных бизнес-сценариях можно использовать «параллельный режим композиции»?
Возьмем в качестве примера «страницу расчета ордера» в «комбинированном режиме» и продолжим рассмотрение страницы расчета ордера определенного востока:
Из формы отображения страницы видно, что:
- Страница состоит из нескольких модулей, таких как:
- Адресный модуль: получить данные об адресе пользователя
- Модуль способов оплаты: Получите список способов оплаты
- Модуль магазина: Получите информацию о магазинах, выбранных товарах в корзинах и т. д.
- Модуль счетов: получите список типов счетов
- Купонный модуль: Получите список пользовательских купонов
- Модуль bean-компонента: получение информации о пользовательских баллах
- Модуль подарочных карт: получите список списков подарочных карт
- Модуль суммы заказа: получение информации о сумме заказа
- Один модуль может состоять из нескольких подмодулей.
- Модуль магазина состоит из следующих модулей:
- Товарный модуль: Получите информацию о выбранных товарах в корзине
- Модуль послепродажного обслуживания: получение послепродажной информации о продуктах
- Преференциальный модуль: получите информацию о преференциальных действиях, в которых участвует продукт.
- Модуль логистики: Получите список способов доставки, поддерживаемых продуктом
- Модуль магазина состоит из следующих модулей:
Согласно бизнес-логике «комбинированный режим» в процессе реализации:
Однако нам совершенно ясно, что между некоторыми модулями нет никакой зависимости.И этот модуль включает в себя блокировку таких операций, как обслуживание удаленных вызовов.,Например:
- Когда адресный модуль вызывает адресную службу для получения данных адреса пользователя.
- Модуль способа оплаты также может считывать Redis для получения данных списка способов оплаты и так далее.
так:Некоторые модули могут выполняться одновременно.
Если вышеуказанные модули без зависимостей модифицируются для одновременного выполнения, мы получаем следующий поток выполнения:
Как использовать «режим параллельной композиции»?
Для процесса моделирования «параллельного режима композиции» вы можете обратиться к предыдущей статье.«Компоненты кода | Go Design Patterns в действии», мы говорим только о местах, на которые нужно обратить внимание.
Ядро «режим параллельной композиции» по-прежнемуComponent
Компонентный интерфейс, давайте сначала посмотрим на «комбинированный режим»Component
Интерфейс компонента выглядит следующим образом (оптимизирован в предыдущей статье, далее инкапсулирован и извлеченBusinessLogicDo
метод):
// Component 组件接口
type Component interface {
// 添加一个子组件
Mount(c Component, components ...Component) error
// 移除一个子组件
Remove(c Component) error
// 执行当前组件业务和执行子组件
// ctx 业务上下文
// currentConponent 当前组件
Do(ctx *Context, currentConponent Component) error
// 执行当前组件业务业务逻辑
BusinessLogicDo(ctx *Context) error
// 执行子组件
ChildsDo(ctx *Context) error
}
Давайте взглянем на интерфейс компонента Component` в «режиме параллельной композиции» следующим образом (обратите внимание на разницу с «режимом комбинирования»):
// Component 组件接口
type Component interface {
// 添加一个子组件
Mount(c Component, components ...Component) error
// 移除一个子组件
Remove(c Component) error
// 执行当前组件业务:`BusinessLogicDo`和执行子组件:`ChildsDo`
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的WaitGroup对象
// 区别1:增加了WaitGroup对象参数,目的是等待并发子组件的执行完成。
Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
// 执行当前组件业务逻辑
// resChan 回写当前组件业务执行结果的channel
// 区别2:增加了一个channel参数,目的是并发组件执行逻辑时引入了超时机制,需要一个channel接受组件的执行结果
BusinessLogicDo(resChan chan interface{}) error
// 执行子组件
ChildsDo(ctx *Context) error
}
Рассмотрим его подробнее.По сравнению с «комбинированным режимом», после введения параллелизма, нам необходимо акцентировать внимание на следующих моментах:
- Параллельный генерирующий компонент должен установить тайм-аут: предотвращение слишком длительного времени выполнения подкомпонента, ключевое слово решения
context.WithTimeout
- Различайте общие компоненты и параллельные компоненты: синтезируйте и повторно используйте базовые компоненты и инкапсулируйте их как параллельные базовые компоненты.
- Родительские компоненты с параллельными подкомпонентами должны дождаться завершения выполнения параллельных подкомпонентов (включая время ожидания), ключевое слово решения
sync.WaitGroup
- Параллельные подкомпоненты должны обнаруживать тайм-ауты при выполнении собственной бизнес-логики: чтобы выполнение бизнес-логики внутри подкомпонентов не занимало слишком много времени, ключевые слова решения
select
и<-ctx.Done()
Первый момент: параллельным подкомпонентам необходимо установить тайм-аут
// Context 业务上下文
type Context struct {
// context.WithTimeout派生的子上下文
TimeoutCtx context.Context
// 超时函数
context.CancelFunc
}
Второй момент: различайте обычные компоненты и параллельные компоненты
Добавить новую базовую структуру компонентов параллелизмаBaseConcurrencyComponent
, а также синтезировать и повторно использовать базовые компоненты в «режиме комбинирования».BaseComponent
,следующее:
// BaseConcurrencyComponent 并发基础组件
type BaseConcurrencyComponent struct {
// 合成复用基础组件
BaseComponent
// 当前组件是否有并发子组件
HasChildConcurrencyComponents bool
// 并发子组件列表
ChildConcurrencyComponents []Component
// wg 对象
*sync.WaitGroup
// 当前组件业务执行结果channel
logicResChan chan interface{}
// 当前组件执行过程中的错误信息
Err error
}
Третий момент: родительский компонент с параллельными подкомпонентами должен дождаться завершения выполнения параллельного подкомпонента (включая тайм-аут)
Измените «комбинированный режим» вChildsDo
метод для поддержки одновременного выполнения подкомпонентов, основные модификации и реализации заключаются в следующем:
- пройти через
go
Подкомпонент выполнения ключевых слов - пройти через
*WaitGroup.Wait()
Ожидание результата выполнения дочернего компонента
// ChildsDo 执行子组件
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
if bc.WaitGroup == nil {
bc.WaitGroup = &sync.WaitGroup{}
}
// 执行并发子组件
for _, childComponent := range bc.ChildConcurrencyComponents {
bc.WaitGroup.Add(1)
go childComponent.Do(ctx, childComponent, bc.WaitGroup)
}
// 执行子组件
for _, childComponent := range bc.ChildComponents {
if err = childComponent.Do(ctx, childComponent, nil); err != nil {
return err
}
}
if bc.HasChildConcurrencyComponents {
// 等待并发组件执行结果
bc.WaitGroup.Wait()
}
return
}
Четвертый пункт: параллельные подкомпоненты должны обнаруживать тайм-ауты при выполнении собственной бизнес-логики.
select
Канал, возвращаемый схемой Done() подконтекста, полученной из ключевого слова context.WithTimeout(), будет закрыт, когда произойдет тайм-аут. Конкретный код реализации выглядит следующим образом:
// Do 执行子组件
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
defer wg.Done()
// 初始化并发子组件channel
if bc.logicResChan == nil {
bc.logicResChan = make(chan interface{}, 1)
}
go currentConponent.BusinessLogicDo(bc.logicResChan)
select {
// 等待业务执行结果
case <-bc.logicResChan:
// 业务执行结果
fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
break
// 超时等待
case <-ctx.TimeoutCtx.Done():
// 超时退出
fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
bc.Err = ErrConcurrencyComponentTimeout
break
}
// 执行子组件
err = currentConponent.ChildsDo(ctx)
return
}
демонстрация кода
package main
import (
"context"
"errors"
"fmt"
"net/http"
"reflect"
"sync"
"time"
)
//------------------------------------------------------------
//Go设计模式实战系列
//组合模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
//example:
// 创建一个根组件
// 如果子组件存在并发组件则父组件必须为并发组件
// type RootComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *RootComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // do nothing
// return
// }
//
// 创建一个并发组件
// type DemoConcurrenyComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *DemoConcurrenyComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // 并发组件业务逻辑填充到这
// return
// }
//
// 创建一个普通组件
// type DemoComponent struct {
// BaseComponent
// }
//
// func (bc *DemoComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // 普通组件业务逻辑填充到这
// return
// }
//
// // 普通组件
// root.Mount(
// &DemoComponent{},
// )
//
// // 并发组件
// root := &RootComponent{}
// root.MountConcurrency(
// &DemoConcurrenyComponent{},
// )
//
// // 初始化业务上下文 并设置超时时间
// ctx := GetContext(5 * time.Second)
// defer ctx.CancelFunc()
// // 开始执行子组件
// root.ChildsDo(ctx)
var (
// ErrConcurrencyComponentTimeout 并发组件业务超时
ErrConcurrencyComponentTimeout = errors.New("Concurrency Component Timeout")
)
// Context 业务上下文
type Context struct {
// context.WithTimeout派生的子上下文
TimeoutCtx context.Context
// 超时函数
context.CancelFunc
}
// GetContext 获取业务上下文实例
// d 超时时间
func GetContext(d time.Duration) *Context {
c := &Context{}
c.TimeoutCtx, c.CancelFunc = context.WithTimeout(context.Background(), d)
return c
}
// Component 组件接口
type Component interface {
// 添加一个子组件
Mount(c Component, components ...Component) error
// 移除一个子组件
Remove(c Component) error
// 执行当前组件业务:`BusinessLogicDo`和执行子组件:`ChildsDo`
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
// 执行当前组件业务逻辑
// resChan 回写当前组件业务执行结果的channel
BusinessLogicDo(resChan chan interface{}) error
// 执行子组件
ChildsDo(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 执行子组件
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
func (bc *BaseComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
//执行当前组件业务代码
err = currentConponent.BusinessLogicDo(nil)
if err != nil {
return err
}
// 执行子组件
return currentConponent.ChildsDo(ctx)
}
// BusinessLogicDo 当前组件业务逻辑代码填充处
func (bc *BaseComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// do nothing
return
}
// ChildsDo 执行子组件
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
// 执行子组件
for _, childComponent := range bc.ChildComponents {
if err = childComponent.Do(ctx, childComponent, nil); err != nil {
return err
}
}
return
}
// BaseConcurrencyComponent 并发基础组件
type BaseConcurrencyComponent struct {
// 合成复用基础组件
BaseComponent
// 当前组件是否有并发子组件
HasChildConcurrencyComponents bool
// 并发子组件列表
ChildConcurrencyComponents []Component
// wg 对象
*sync.WaitGroup
// 当前组件业务执行结果channel
logicResChan chan interface{}
// 当前组件执行过程中的错误信息
Err error
}
// Remove 移除一个子组件
func (bc *BaseConcurrencyComponent) 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:]...)
}
}
for k, childComponent := range bc.ChildConcurrencyComponents {
if c == childComponent {
fmt.Println(runFuncName(), "移除:", reflect.TypeOf(childComponent))
bc.ChildConcurrencyComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
}
}
return
}
// MountConcurrency 挂载一个并发子组件
func (bc *BaseConcurrencyComponent) MountConcurrency(c Component, components ...Component) (err error) {
bc.HasChildConcurrencyComponents = true
bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, c)
if len(components) == 0 {
return
}
bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, components...)
return
}
// ChildsDo 执行子组件
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
if bc.WaitGroup == nil {
bc.WaitGroup = &sync.WaitGroup{}
}
// 执行并发子组件
for _, childComponent := range bc.ChildConcurrencyComponents {
bc.WaitGroup.Add(1)
go childComponent.Do(ctx, childComponent, bc.WaitGroup)
}
// 执行子组件
for _, childComponent := range bc.ChildComponents {
if err = childComponent.Do(ctx, childComponent, nil); err != nil {
return err
}
}
if bc.HasChildConcurrencyComponents {
// 等待并发组件执行结果
bc.WaitGroup.Wait()
}
return
}
// Do 执行子组件
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
defer wg.Done()
// 初始化并发子组件channel
if bc.logicResChan == nil {
bc.logicResChan = make(chan interface{}, 1)
}
go currentConponent.BusinessLogicDo(bc.logicResChan)
select {
// 等待业务执行结果
case <-bc.logicResChan:
// 业务执行结果
fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
break
// 超时等待
case <-ctx.TimeoutCtx.Done():
// 超时退出
fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
bc.Err = ErrConcurrencyComponentTimeout
break
}
// 执行子组件
err = currentConponent.ChildsDo(ctx)
return
}
// CheckoutPageComponent 订单结算页面组件
type CheckoutPageComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 当前组件业务逻辑代码填充处
func (bc *CheckoutPageComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "订单结算页面组件...")
return
}
// AddressComponent 地址组件
type AddressComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *AddressComponent) BusinessLogicDo(resChan chan interface{}) error {
fmt.Println(runFuncName(), "地址组件...")
fmt.Println(runFuncName(), "获取地址信息 ing...")
// 模拟远程调用地址服务
http.Get("http://example.com/")
resChan <- struct{}{} // 写入业务执行结果
fmt.Println(runFuncName(), "获取地址信息 done...")
return nil
}
// PayMethodComponent 支付方式组件
type PayMethodComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *PayMethodComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "支付方式组件...")
fmt.Println(runFuncName(), "获取支付方式 ing...")
// 模拟远程调用地址服务 略
resChan <- struct{}{}
fmt.Println(runFuncName(), "获取支付方式 done...")
return nil
}
// StoreComponent 店铺组件
type StoreComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *StoreComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "店铺组件...")
return
}
// SkuComponent 商品组件
type SkuComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *SkuComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "商品组件...")
return
}
// PromotionComponent 优惠信息组件
type PromotionComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *PromotionComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "优惠信息组件...")
return
}
// ExpressComponent 物流组件
type ExpressComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *ExpressComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "物流组件...")
return
}
// AftersaleComponent 售后组件
type AftersaleComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *AftersaleComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "售后组件...")
return
}
// InvoiceComponent 发票组件
type InvoiceComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *InvoiceComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "发票组件...")
fmt.Println(runFuncName(), "获取发票信息 ing...")
// 模拟远程调用地址服务 略
resChan <- struct{}{} // 写入业务执行结果
fmt.Println(runFuncName(), "获取发票信息 done...")
return
}
// CouponComponent 优惠券组件
type CouponComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *CouponComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "优惠券组件...")
fmt.Println(runFuncName(), "获取最优优惠券 ing...")
// 模拟远程调用优惠券服务
http.Get("http://example.com/")
// 写入业务执行结果
resChan <- struct{}{}
fmt.Println(runFuncName(), "获取最优优惠券 done...")
return
}
// GiftCardComponent 礼品卡组件
type GiftCardComponent struct {
// 合成复用基础组件
BaseConcurrencyComponent
}
// BusinessLogicDo 并发组件实际填充业务逻辑的地方
func (bc *GiftCardComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "礼品卡组件...")
fmt.Println(runFuncName(), "获取礼品卡信息 ing...")
// 模拟远程调用地址服务 略
resChan <- struct{}{} // 写入业务执行结果
fmt.Println(runFuncName(), "获取礼品卡信息 done...")
return
}
// OrderComponent 订单金额详细信息组件
type OrderComponent struct {
// 合成复用基础组件
BaseComponent
}
// BusinessLogicDo 当前组件业务逻辑代码填充处
func (bc *OrderComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// 当前组件的业务逻辑写这
fmt.Println(runFuncName(), "订单金额详细信息组件...")
return
}
// Demo 示例
func Demo() {
// 初始化订单结算页面 这个大组件
checkoutPage := &CheckoutPageComponent{}
// 挂载子组件
storeComponent := &StoreComponent{}
skuComponent := &SkuComponent{}
skuComponent.Mount(
&PromotionComponent{},
&AftersaleComponent{},
)
storeComponent.Mount(
skuComponent,
&ExpressComponent{},
)
// ---挂载组件---
// 普通组件
checkoutPage.Mount(
storeComponent,
&OrderComponent{},
)
// 并发组件
checkoutPage.MountConcurrency(
&AddressComponent{},
&PayMethodComponent{},
&InvoiceComponent{},
&CouponComponent{},
&GiftCardComponent{},
)
// 初始化业务上下文 并设置超时时间
ctx := GetContext(5 * time.Second)
defer ctx.CancelFunc()
// 开始构建页面组件数据
checkoutPage.ChildsDo(ctx)
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU() - 1)
Demo()
}
// 获取正在运行的函数名
func runFuncName() string {
pc := make([]uintptr, 1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
return ""
}
Результат запуска кода:
Running] go run "../easy-tips/go/patterns/composite/concurrency/composite-concurrency.go"
main.(*StoreComponent).BusinessLogicDo 店铺组件...
main.(*SkuComponent).BusinessLogicDo 商品组件...
main.(*PromotionComponent).BusinessLogicDo 优惠信息组件...
main.(*AftersaleComponent).BusinessLogicDo 售后组件...
main.(*ExpressComponent).BusinessLogicDo 物流组件...
main.(*OrderComponent).BusinessLogicDo 订单金额详细信息组件...
main.(*PayMethodComponent).BusinessLogicDo 支付方式组件...
main.(*PayMethodComponent).BusinessLogicDo 获取支付方式 ing...
main.(*InvoiceComponent).BusinessLogicDo 发票组件...
main.(*InvoiceComponent).BusinessLogicDo 获取发票信息 ing...
main.(*GiftCardComponent).BusinessLogicDo 礼品卡组件...
main.(*GiftCardComponent).BusinessLogicDo 获取礼品卡信息 ing...
main.(*CouponComponent).BusinessLogicDo 优惠券组件...
main.(*CouponComponent).BusinessLogicDo 获取发票信息 ing...
main.(*AddressComponent).BusinessLogicDo 地址组件...
main.(*AddressComponent).BusinessLogicDo 获取地址信息 ing...
main.(*InvoiceComponent).BusinessLogicDo 获取发票信息 done...
main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...
main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...
main.(*PayMethodComponent).BusinessLogicDo 获取支付方式 done...
main.(*AddressComponent).BusinessLogicDo 获取地址信息 done...
main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...
main.(*CouponComponent).BusinessLogicDo 获取发票信息 done...
main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...
main.(*GiftCardComponent).BusinessLogicDo 获取礼品卡信息 done...
main.(*BaseConcurrencyComponent).Do bc.BusinessLogicDo wait.done...
Сравнительный анализ «комбинированного режима» и «параллельного комбинированного режима»
Код эталона:
package composite
import (
"easy-tips/go/patterns/composite/concurrency"
"easy-tips/go/patterns/composite/normal"
"runtime"
"testing"
)
// go test -benchmem -run=^$ easy-tips/go/patterns/composite -bench . -v -count=1 --benchtime 20s
func Benchmark_Normal(b *testing.B) {
b.SetParallelism(runtime.NumCPU())
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
normal.Demo()
}
})
}
func Benchmark_Concurrency(b *testing.B) {
b.SetParallelism(runtime.NumCPU())
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
concurrency.Demo()
}
})
}
Результаты сравнительного теста локальной машины:
(TIGERB) 🤔 ➜ composite git:(master) ✗ go test -benchmem -run=^$ easy-tips/go/patterns/composite -bench . -v -count=1 --benchtime 20s
goos: darwin
goarch: amd64
pkg: easy-tips/go/patterns/composite
Benchmark_Normal-4 376 56666895 ns/op 35339 B/op 286 allocs/op
Benchmark_Concurrency-4 715 32669301 ns/op 36445 B/op 299 allocs/op
PASS
ok easy-tips/go/patterns/composite 68.835s
Как видно из приведенных выше результатов тестовBenchmark_Concurrency-4
Среднее время выполнения за раз составляет32669301 ns
Это лучше чемBenchmark_Normal
из56666895 ns
.
Эпилог
«Шаблон параллельной композиции» — это «новый шаблон», образованный конкретным шаблоном проектирования в сочетании с присущими языку Go функциями параллелизма посредством соответствующей инкапсуляции.
Приложение «Режим параллельного комбинирования» Базовый шаблон кода и инструкции
//------------------------------------------------------------
//Go设计模式实战系列
//组合模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
//example:
// 创建一个根组件
// 如果子组件存在并发组件则父组件必须为并发组件
// type RootComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *RootComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // do nothing
// return
// }
//
// 创建一个并发组件
// type DemoConcurrenyComponent struct {
// BaseConcurrencyComponent
// }
//
// func (bc *DemoConcurrenyComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // 并发组件业务逻辑填充到这
// return
// }
//
// 创建一个普通组件
// type DemoComponent struct {
// BaseComponent
// }
//
// func (bc *DemoComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// // 普通组件业务逻辑填充到这
// return
// }
//
// // 普通组件
// root.Mount(
// &DemoComponent{},
// )
//
// // 并发组件
// root := &RootComponent{}
// root.MountConcurrency(
// &DemoConcurrenyComponent{},
// )
//
// // 初始化业务上下文 并设置超时时间
// ctx := GetContext(5 * time.Second)
// defer ctx.CancelFunc()
// // 开始执行子组件
// root.ChildsDo(ctx)
var (
// ErrConcurrencyComponentTimeout 并发组件业务超时
ErrConcurrencyComponentTimeout = errors.New("Concurrency Component Timeout")
)
// Context 业务上下文
type Context struct {
// context.WithTimeout派生的子上下文
TimeoutCtx context.Context
// 超时函数
context.CancelFunc
}
// GetContext 获取业务上下文实例
// d 超时时间
func GetContext(d time.Duration) *Context {
c := &Context{}
c.TimeoutCtx, c.CancelFunc = context.WithTimeout(context.Background(), d)
return c
}
// Component 组件接口
type Component interface {
// 添加一个子组件
Mount(c Component, components ...Component) error
// 移除一个子组件
Remove(c Component) error
// 执行当前组件业务:`BusinessLogicDo`和执行子组件:`ChildsDo`
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) error
// 执行当前组件业务逻辑
// resChan 回写当前组件业务执行结果的channel
BusinessLogicDo(resChan chan interface{}) error
// 执行子组件
ChildsDo(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 执行子组件
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
func (bc *BaseComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
//执行当前组件业务代码
err = currentConponent.BusinessLogicDo(nil)
if err != nil {
return err
}
// 执行子组件
return currentConponent.ChildsDo(ctx)
}
// BusinessLogicDo 当前组件业务逻辑代码填充处
func (bc *BaseComponent) BusinessLogicDo(resChan chan interface{}) (err error) {
// do nothing
return
}
// ChildsDo 执行子组件
func (bc *BaseComponent) ChildsDo(ctx *Context) (err error) {
// 执行子组件
for _, childComponent := range bc.ChildComponents {
if err = childComponent.Do(ctx, childComponent, nil); err != nil {
return err
}
}
return
}
// BaseConcurrencyComponent 并发基础组件
type BaseConcurrencyComponent struct {
// 合成复用基础组件
BaseComponent
// 当前组件是否有并发子组件
HasChildConcurrencyComponents bool
// 并发子组件列表
ChildConcurrencyComponents []Component
// wg 对象
*sync.WaitGroup
// 当前组件业务执行结果channel
logicResChan chan interface{}
// 当前组件执行过程中的错误信息
Err error
}
// Remove 移除一个子组件
func (bc *BaseConcurrencyComponent) 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:]...)
}
}
for k, childComponent := range bc.ChildConcurrencyComponents {
if c == childComponent {
fmt.Println(runFuncName(), "移除:", reflect.TypeOf(childComponent))
bc.ChildConcurrencyComponents = append(bc.ChildComponents[:k], bc.ChildComponents[k+1:]...)
}
}
return
}
// MountConcurrency 挂载一个并发子组件
func (bc *BaseConcurrencyComponent) MountConcurrency(c Component, components ...Component) (err error) {
bc.HasChildConcurrencyComponents = true
bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, c)
if len(components) == 0 {
return
}
bc.ChildConcurrencyComponents = append(bc.ChildConcurrencyComponents, components...)
return
}
// ChildsDo 执行子组件
func (bc *BaseConcurrencyComponent) ChildsDo(ctx *Context) (err error) {
if bc.WaitGroup == nil {
bc.WaitGroup = &sync.WaitGroup{}
}
// 执行并发子组件
for _, childComponent := range bc.ChildConcurrencyComponents {
bc.WaitGroup.Add(1)
go childComponent.Do(ctx, childComponent, bc.WaitGroup)
}
// 执行子组件
for _, childComponent := range bc.ChildComponents {
if err = childComponent.Do(ctx, childComponent, nil); err != nil {
return err
}
}
if bc.HasChildConcurrencyComponents {
// 等待并发组件执行结果
bc.WaitGroup.Wait()
}
return
}
// Do 执行子组件
// ctx 业务上下文
// currentConponent 当前组件
// wg 父组件的waitgroup对象
func (bc *BaseConcurrencyComponent) Do(ctx *Context, currentConponent Component, wg *sync.WaitGroup) (err error) {
defer wg.Done()
// 初始化并发子组件channel
if bc.logicResChan == nil {
bc.logicResChan = make(chan interface{}, 1)
}
go currentConponent.BusinessLogicDo(bc.logicResChan)
select {
// 等待业务执行结果
case <-bc.logicResChan:
// 业务执行结果
fmt.Println(runFuncName(), "bc.BusinessLogicDo wait.done...")
break
// 超时等待
case <-ctx.TimeoutCtx.Done():
// 超时退出
fmt.Println(runFuncName(), "bc.BusinessLogicDo timeout...")
bc.Err = ErrConcurrencyComponentTimeout
break
}
// 执行子组件
err = currentConponent.ChildsDo(ctx)
return
}
特别说明:
本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。
Список статей
- Шаблоны кода | Шаблоны Go Design в действии
- Связанные вызовы | Шаблоны Go Design в действии
- Компоненты кода | Шаблоны Go Design в действии
- Подпишитесь на уведомления | Go Design Patterns в действии
- Решения клиентов | Go Design Patterns в действии
- Преобразование состояния | Шаблоны проектирования Go в действии
Go Design Pattern Combat Series Больше статей нажмите здесь, чтобы просмотреть