这种链式的写法,是否易于 CR 或者维护呢?纠结中

2023-04-18 11:06:01 +08:00
 sunshinev

这里对需求进行了简化,实际上真实的业务需求每个Condition包含非常复杂的逻辑,如果全都用 if 嵌套来实现,只能拆分成一个一个的方法

后来想到能否直接表示出整个链条,就改写成了下面的样子,求各位大佬帮忙看下这种写法的优缺点, 如果有更好的方法, 也请大佬们指点一二

package main

import (
	"log"
)

type ConditionUnitItf interface {
	Condition(interface{}) (bool, error)
}

type ConditionUnitWrapper struct {
	Params        interface{}
	Result        interface{}
	ConditionUnit ConditionUnitItf
	TrueHandler   *ConditionUnitWrapper
	FalseHandler  *ConditionUnitWrapper
}

// NewUnit ...
func NewUnit(unit ConditionUnitItf) *ConditionUnitWrapper {
	return &ConditionUnitWrapper{
		ConditionUnit: unit,
	}
}

// True 设置这个实例的下一个 handler
func (s *ConditionUnitWrapper) True(nextHandler *ConditionUnitWrapper) *ConditionUnitWrapper {
	s.TrueHandler = nextHandler.WithParams(s.Params)
	return s
}

func (s *ConditionUnitWrapper) False(nextHandler *ConditionUnitWrapper) *ConditionUnitWrapper {
	s.FalseHandler = nextHandler
	return s
}

// WithParams 给实例设置参数
func (s *ConditionUnitWrapper) WithParams(params interface{}) *ConditionUnitWrapper {
	s.Params = params
	return s
}

func (s *ConditionUnitWrapper) SetResult(r interface{}) *ConditionUnitWrapper {
	s.Result = r
	return s
}

// Run 构造 chain 的核心逻辑,除了叶子节点,其他的 Condition Unit 都使用了 Condition Base 的 Run 方法
func (s *ConditionUnitWrapper) Run() (*ConditionUnitWrapper, error) {
	isTrue, err := s.ConditionUnit.Condition(s.Params)
	if err != nil {
		return nil, err
	}

	if isTrue && s.TrueHandler != nil {
		// 这里使用 WithParams 将参数传递给 handler
		return s.TrueHandler.WithParams(s.Params).Run()
	}

	if !isTrue && s.FalseHandler != nil {
		return s.FalseHandler.WithParams(s.Params).Run()
	}

	return s, nil
}

// ===================== 上面是实现了,下面要实现多个 条件单元
type userParams struct {
	IsInCompany      bool   // 工作时间是否小于 10 小时
	Destination      string // 目的地是 家 还是 餐厅
	IsNeedWorkAtHome bool   // 是否在家要工作
	IsHungry         bool   // 是否饿了
	LastAction       int    // 最后的结果行为
}

// 定义 3 个行为
const (
	HumanActionWork   = iota // 工作
	HumanActionEating        // 吃饭
	HumanActionRest          // 休息
)

// 下面我要构造条件链路,按照当前的 user 状态(userParams) 来计算下一步的行为

// 条件链路包含的单元为
// 1. 是否在公司
// 2. 是否回家了
// 3. 是否饿了
// 4. 是否继续工作

// 上面出现了 4 个条件判断,所以下面我要创建四个条件单元

// 是否下班
type UnitIsInCompany struct {
}

// 实际的代码逻辑中,每一个 Condition 可能会包含更加复杂的逻辑
func (s UnitIsInCompany) Condition(params interface{}) (bool, error) {
	return params.(userParams).IsInCompany, nil
}

// 是否回家
type UnitIsDestinationHome struct {
}

func (s UnitIsDestinationHome) Condition(params interface{}) (bool, error) {
	return params.(userParams).Destination == "home", nil
}

// 是否饿了
type UnitIsHungry struct {
}

func (s UnitIsHungry) Condition(params interface{}) (bool, error) {
	return params.(userParams).IsHungry, nil
}

// 是否需要继续工作
type UnitIsNeedWorkAtHome struct {
}

func (s UnitIsNeedWorkAtHome) Condition(params interface{}) (bool, error) {

	if params.(userParams).IsNeedWorkAtHome {
		return true, nil
	}
	return false, nil
}

// ===================== 下面还要针对条件写叶子结点的结构

// 对于叶子节点而言, 主要是用于收尾,调用 SetResult
type LeafUnitEchoAction struct {
}

func (s LeafUnitEchoAction) Condition(params interface{}) (bool, error) {
	return true, nil
}

func main() {
	user := userParams{
		IsInCompany:      false,
		Destination:      "home",
		IsNeedWorkAtHome: false,
		IsHungry:         false,
	}
	// 按照条件构造的链路为如下:
	obj, err := NewUnit(UnitIsInCompany{}).WithParams(user).
		// 如果在公司,就工作
		True(NewUnit(LeafUnitEchoAction{}).SetResult(HumanActionWork)).
		// 如果不在公司,判断目的地是否是 Home
		False(
			NewUnit(UnitIsDestinationHome{}).
				// 回家后是否需要继续工作
				True(
					NewUnit(UnitIsNeedWorkAtHome{}).
						// 继续工作
						True(NewUnit(LeafUnitEchoAction{}).SetResult(HumanActionWork)).
						// 不工作就休息
						False(NewUnit(LeafUnitEchoAction{}).SetResult(HumanActionRest))).
				// 不回家,是否饿了
				False(NewUnit(UnitIsHungry{}).
					// 饿了就去餐厅吃饭
					True(NewUnit(LeafUnitEchoAction{}).SetResult(HumanActionEating)).
					// 不饿就休息
					False(NewUnit(LeafUnitEchoAction{}).SetResult(HumanActionRest)))).
		Run()

	if err != nil {
		log.Fatalf("err is %v", err)
	}

	// 最后通过 GetParams 获取到 LastAction
	log.Printf("action is %v", obj.Result)
}

1609 次点击
所在节点    Go 编程语言
2 条回复
sunshinev
2023-04-18 11:09:13 +08:00
还有这种,是不是降低了圈复杂度。。。。
mrgeneral
2023-04-18 11:50:53 +08:00
看着有比较明显的范式:满足 condition 1 执行 action 1 。

做一下接口抽象,责任链来编排一下,具体的编排通过配置文件来实现,交给产品、业务自己去配置,不要 hardcode 翻译到代码里面。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/933393

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX