设计模式

概述

参考:

Design pattern(设计模式) 代表了最佳的实践。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的“四人组”(Gang of Four,GoF)匿名著称。

有关软件设计模式的定义很多,有些从模式的特点来说明,有些从模式的作用来说明。本教程给出的定义是大多数学者公认的,从以下两个方面来说明。

1. 软件设计模式的概念

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。

2. 学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点:

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

当然,软件设计模式只是一个引导。在具体的软件开发中,必须根据设计的应用系统的特点和要求来恰当选择。对于简单的程序开发,可能写一个简单的算法要比引入某种设计模式更加容易。但对大项目的开发或者框架设计,用设计模式来组织代码显然更好。

装饰模式

Decorator(装饰器)

策略模式

用来解决过多 if else 逻辑的模式

// 假如现在有如下条件
if FirstCondition {
    // 条件一
} else if SecondCondition {
    // 条件二
}

策略指:当满足 XX 条件,执行 YY 行为。

也就是说,可以将上面的 if else 中的 Condition(条件) 转为 Strategy(策略,i.e. 结构体),为该策略添加一些行为(方法),比如判断策略是否满足,策略如何执行

// 策略模式
func main() {
	a := 1
	b := 2
	c := 3
	var (
		conditionOne   bool = func() bool { return a == 1 }()
		conditionTwo   bool = func() bool { return b == 2 }()
		conditionThree bool = func() bool { return c == 3 }()
	)

	if conditionOne {
		fmt.Println("满足条件 1 后执行")
		if conditionTwo {
			fmt.Println("满足条件 2 后执行")
		}
	} else if conditionThree {
		fmt.Println("满足条件 3 后执行")
	} else {
		fmt.Println("都不满足后执行")
	}

	// 使用设计模式实现上述 if else 逻辑
	strategyPattern()
}

// 策略模式就是将 if else 中每个条件判断抽象为一个 对象(i.e. go 的 struct)。
// 这些 条件对象 通常要包含两个方法: 1. 判断策略是否应该执行 2. 策略执行的具体逻辑
//
// 多个 条件对象 要实现一个接口。
type Strategy interface {
	IsMatch(data *StrategyData) bool // 1. 判断策略是否应该执行。相当于 if else 中的条件判断
	Matched(data *StrategyData)      // 2. 策略执行的具体逻辑。相当于 if else 中的具体逻辑
	// ... 其他方法,比如:优先级排序、条件判断的依赖关系、策略失败时的处理、etc.
	NotMatched(data *StrategyData) // 3. 策略不执行时的处理
}

// 通常一个方法里应该包含一个策略数据的参数
type StrategyData struct {
	a int
	b int
	c int
}

// ######## 定义 条件对象 作为策略 ########
//
// 策略1
type StrategyOne struct{}

func (c *StrategyOne) IsMatch(data *StrategyData) bool {
	return data.a == 1
}
func (c *StrategyOne) Matched(data *StrategyData) {
	fmt.Println("策略模式满足条件 1 后执行")
}
func (c *StrategyOne) NotMatched(data *StrategyData) {
	fmt.Println("策略模式不满足条件 1 后执行")
}

// 策略2
type StrategyTwo struct{}

func (c *StrategyTwo) IsMatch(data *StrategyData) bool {
	return data.b == 2
}
func (c *StrategyTwo) Matched(data *StrategyData) {
	fmt.Println("策略模式满足条件 2 后执行")
}
func (c *StrategyTwo) NotMatched(data *StrategyData) {
	fmt.Println("策略模式不满足条件 2 后执行")
}

// 策略3
type StrategyThree struct{}

func (c *StrategyThree) IsMatch(data *StrategyData) bool {
	return data.c == 3
}
func (c *StrategyThree) Matched(data *StrategyData) {
	fmt.Println("策略模式满足条件 3 后执行")
}
func (c *StrategyThree) NotMatched(data *StrategyData) {
	fmt.Println("策略模式不满足条件 3 后执行")
}

// ######## 定义策略结束 ########

// 定义一个策略管理器,管理多个策略
type StrategyManager struct {
	strategies []Strategy
}

// 实例化一个策略管理器,为其添加多个策略
func NewStrategyManager() *StrategyManager {
	return &StrategyManager{
		strategies: []Strategy{
			&StrategyOne{},
			&StrategyTwo{},
			&StrategyThree{},
		},
	}
}

// Process 处理策略的逻辑。在这里遍历所有策略,找到匹配的策略并执行。就像 if-else 也要一个一个判断一样。
// 但是,如果策略很多(if else 条件很多),if else 会显得很臃肿,
// 尤其对于嵌套的 if else,策略模式会让代码看起来更清晰。这时候策略模式就会让代码看起来很优雅,可扩展性也很强。
// !!!如果需要添加新的策略,只需要实现 Strategy 接口,并添加到 StrategyManager 中即可!!!
func (m *StrategyManager) Process(data *StrategyData) {
	for _, strategy := range m.strategies {
		if strategy.IsMatch(data) {
			strategy.Matched(data)
		} else {
			strategy.NotMatched(data)
		}
	}
}

// 使用策略模式
func strategyPattern() {
	processor := NewStrategyManager()
	processor.Process(&StrategyData{a: 1, b: 2, c: 4})
}

问 AI

多 if else

https://claude.ai/chat/3d78df88-9fc2-48fe-9118-c359a62c9ed0

问 Claude AI 一:

我想优化一个 go 语言的 if 逻辑

	if dataType.NewestDirectiveID == "" {
		level.Debug(c.logger).Log("msg", "首次获取指令,更新最新指令的 ID")
		dataType.NewestDirectiveID = result.CommandID
	} else if dataType.NewestDirectiveID != result.CommandID {
		level.Debug(c.logger).Log("msg", "发现新指令,更新最新指令的 ID")
        dataType.NewestDirectiveID = result.CommandID
        // 发送通知
    }

这个程序是一个监控指令的程序,当发现新指令时,向外部发送通知。我这种单纯用 if…else if… 逻辑是否需要优化?

问 AI 二:

是否有一种设计模式,可以让 if 条件变多时也能简便得添加条件?

答:

对于处理多个条件判断的情况,有几种常用的设计模式可以帮助我们组织代码。我来介绍几种比较适合你这个场景的模式:

  1. 责任链模式(Chain of Responsibility):
// 定义指令处理器接口
type DirectiveHandler interface {
    Handle(directive *DirectiveData) bool
    SetNext(handler DirectiveHandler)
}

// 基础处理器
type BaseHandler struct {
    next DirectiveHandler
}

func (h *BaseHandler) SetNext(handler DirectiveHandler) {
    h.next = handler
}

// 首次指令处理器
type FirstTimeHandler struct {
    BaseHandler
    logger log.Logger
}

func (h *FirstTimeHandler) Handle(data *DirectiveData) bool {
    if data.NewestDirectiveID == "" {
        level.Debug(h.logger).Log("msg", "首次获取指令,更新最新指令的 ID")
        data.NewestDirectiveID = data.Result.CommandID
        return true
    }
    if h.next != nil {
        return h.next.Handle(data)
    }
    return false
}

// 新指令处理器
type NewDirectiveHandler struct {
    BaseHandler
    logger log.Logger
}

func (h *NewDirectiveHandler) Handle(data *DirectiveData) bool {
    if data.NewestDirectiveID != data.Result.CommandID {
        level.Debug(h.logger).Log("msg", "发现新指令,更新最新指令的 ID")
        data.NewestDirectiveID = data.Result.CommandID
        // 发送通知
        return true
    }
    if h.next != nil {
        return h.next.Handle(data)
    }
    return false
}

// 使用示例
func (c *Client) processDirective(data *DirectiveData) {
    firstHandler := &FirstTimeHandler{logger: c.logger}
    newDirectiveHandler := &NewDirectiveHandler{logger: c.logger}
    
    firstHandler.SetNext(newDirectiveHandler)
    firstHandler.Handle(data)
}
  1. 策略模式(Strategy Pattern):设计遵循了"单一职责原则"和"关注点分离"的设计原则

已总结到上文

  1. 规则引擎模式:
// 规则定义
type Rule struct {
    Name      string
    Condition func(*DirectiveData) bool
    Action    func(*DirectiveData)
}

// 规则引擎
type RuleEngine struct {
    rules []Rule
}

func NewRuleEngine(logger log.Logger) *RuleEngine {
    return &RuleEngine{
        rules: []Rule{
            {
                Name: "FirstTime",
                Condition: func(data *DirectiveData) bool {
                    return data.NewestDirectiveID == ""
                },
                Action: func(data *DirectiveData) {
                    level.Debug(logger).Log("msg", "首次获取指令,更新最新指令的 ID")
                    data.NewestDirectiveID = data.Result.CommandID
                },
            },
            {
                Name: "NewDirective",
                Condition: func(data *DirectiveData) bool {
                    return data.NewestDirectiveID != data.Result.CommandID
                },
                Action: func(data *DirectiveData) {
                    level.Debug(logger).Log("msg", "发现新指令,更新最新指令的 ID")
                    data.NewestDirectiveID = data.Result.CommandID
                    // 发送通知
                },
            },
        },
    }
}

func (e *RuleEngine) Execute(data *DirectiveData) {
    for _, rule := range e.rules {
        if rule.Condition(data) {
            rule.Action(data)
            return
        }
    }
}

// 使用示例
func (c *Client) processDirective(data *DirectiveData) {
    engine := NewRuleEngine(c.logger)
    engine.Execute(data)
}

这些模式各有优势:

  1. 责任链模式:

    • 适合处理有明确顺序的条件判断
    • 每个处理器可以决定是否继续传递给下一个处理器
    • 容易添加新的处理器
  2. 策略模式:

    • 更适合处理互相独立的条件判断
    • 条件判断和处理逻辑分离
    • 容易添加新的策略
  3. 规则引擎模式:

    • 最灵活,可以动态添加规则
    • 规则可以配置化
    • 适合复杂的业务规则处理

考虑到你的场景是监控指令并处理,我个人更推荐使用策略模式,因为:

  1. 各个条件判断相对独立
  2. 代码结构清晰,便于维护
  3. 易于添加新的处理策略
  4. 不需要规则引擎那么重的实现

你觉得这些方案如何?考虑到你的具体业务场景,你觉得哪种模式更适合?


最后修改 November 10, 2024: go 设计模式 (b8fe30a0)