Компоненты кода | Шаблоны Go Design в действии

Go Шаблоны проектирования

Что ж, серия боевых шаблонов дизайна Go, серия golang, фактически используемая бизнесом шаблонов дизайна.

Полная серия нажмите здесь

предисловие

Эта серия в основном рассказывает о том, как использовать шаблоны проектирования в наших реальных бизнес-сценариях.

Эта серия статей в основном имеет следующую структуру:

  • Что такое «Шаблон проектирования XX»?
  • В каких реальных бизнес-сценариях можно использовать «шаблон проектирования XX»?
  • Как использовать «Шаблон дизайна XX»?

В этой статье в основном рассказывается, как «комбинированный режим» используется в реальных бизнес-сценариях.

Что такое «комбинированный режим»?

Объект с иерархической связью состоит из ряда объектов с родительско-дочерней связью через древовидную структуру.

Преимущества комбинированного режима:

  • То, что вы видите, — это то, что вы кодируете: структура кода, которую вы видите, — это реальная иерархическая взаимосвязь бизнеса, такая же, как то, что вы на самом деле видите в интерфейсе пользовательского интерфейса.
  • Пакет высоты: Единственная ответственность.
  • Возможность многократного использования: в различных бизнес-сценариях можно повторно использовать одни и те же компоненты.

В каких реальных бизнес-сценариях можно использовать «комбинированный режим»?

Все сценарии, отвечающие следующим требованиям:

Получить запрос на получение всех интерфейсов данных страницы

Сегодня, когда фронтенд разложен на компоненты, когда мы пишем код интерфейса бэкенда, мы по-прежнему пишем его от начала и до конца согласно бизнес-идее? Можем ли мы подумать: «Как можно легко и быстро разложить на компоненты бизнес-код внутреннего интерфейса?», ответ — да, в этом заключается роль «комбинированного режима».

Мы используем определение «комбинированного режима» и разделение интерфейсных модулей для построения внутренней структуры бизнес-кода:

  • Отдельный модуль внешнего интерфейса -> соответствующий внутренний интерфейс: конкретный отдельный класс -> процесс инкапсуляции
  • Интерфейсный модуль родительско-дочерний компонент -> соответствующий бэкэнд: родительский класс содержит несколько подклассов (отношения без наследования, синтетические отношения повторного использования) -> древовидная структура родительско-дочерних отношений

В каких реальных бизнес-сценариях мы можем использовать «комбинированную модель»?

Например, возьмем в качестве примера "страницу расчета сложной заявки". Ниже приведена страница расчета заявки определенного донга:

Из формы отображения страницы видно, что:

  • Страница состоит из нескольких модулей, таких как:
    • адресный модуль
    • Модуль способа оплаты
    • модуль магазина
    • Модуль счета
    • купонный модуль
    • компонентный модуль
    • модуль подарочной карты
    • Модуль суммы заказа
  • Один модуль может состоять из нескольких подмодулей.
    • Модуль магазина состоит из следующих модулей:
      • товарный модуль
      • Модуль послепродажного обслуживания
      • Модуль предложения
      • Логистический модуль

Как использовать «комбинированный режим»?

Что касается того, как его использовать, вполне возможно скопировать четыре шага, которые я суммировал для использования шаблонов проектирования:

  • Деловой кардинг
  • Блок-схема бизнеса
  • моделирование кода
  • демонстрация кода

Деловой кардинг

На примере страницы расчета заказа некоего востока выше мы получаем следующую схему состава модуля страницы расчета заказа:

Примечание. Модули не обязательно полностью точны.

моделирование кода

Основные классы модели цепочки ответственности в основном включают следующие функции:

  • Свойства члена
    • 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. 本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。