V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
sunshinev
V2EX  ›  Go 编程语言

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

  •  
  •   sunshinev ·
    sunshinev · 2023-04-18 11:06:01 +08:00 · 1622 次点击
    这是一个创建于 592 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这里对需求进行了简化,实际上真实的业务需求每个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)
    }
    
    
    2 条回复    2023-04-18 11:50:53 +08:00
    sunshinev
        1
    sunshinev  
    OP
       2023-04-18 11:09:13 +08:00
    还有这种,是不是降低了圈复杂度。。。。
    mrgeneral
        2
    mrgeneral  
       2023-04-18 11:50:53 +08:00
    看着有比较明显的范式:满足 condition 1 执行 action 1 。

    做一下接口抽象,责任链来编排一下,具体的编排通过配置文件来实现,交给产品、业务自己去配置,不要 hardcode 翻译到代码里面。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2038 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 00:23 · PVG 08:23 · LAX 16:23 · JFK 19:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.