Golang利用策略模式优化if…else和switch

都知道大量 if else 对代码维护和设计都及其不友好,即便是你换成 switch 也并不那么理想,这里推荐主要利用策略模式优化。

需求:比如有20个甚至更多的消息模板,每个模板ID 对应要操作都可能不一样,比如文案,参数计算等。如果一个一个去独立写对应函数然后if else继续条件流程调用显得格外鸡肋,那么请用策略模式优化吧。

策略模式:

策略模式(strategy pattern) 又叫为政策模式(policy pattern),它将定义的算法家族分别封装起来,让它们之间可以互相切换,让算法的变化不会影响到使用算法的用户,属于 行为型设计模式。

原理就是面向对象的继承和多态,实现同一行为在不同条件场景下具备不同的实现。

应用场景:

比如:在你支付时选择支付方式有 微信支付、支付宝支付、京东支付、银行卡支付等

解决在多重相似算法情况下使用了 if…else 和 switch…case 带来的复杂性和臃肿性问题。

策略模式适用于以下场景:

  1. 针对同一类型问题,有多种处理方式,每一种都能独立解决问题。
  2. 需要自由切换选择不同类型算法场景。
  3. 需要闭屏算法具体实现规则场景。

具体组长模块:

一般要实现一个较为完整的策略模式,需要如下组成单元:

  1. 上下文控制函数:用来拒绝切换不同算法,屏蔽高层模块(调用者)对策略、算法的直接访问,封装可能存在的变化–可以用简单工程与单例模式封装该函数
  2. 抽象要实现的策略接口:定义一个interface,决定好内部包含的具体函数方法定义。
  3. 具体的策略角色:实现每个类实现抽象接口的方法,进行内部具体算法的维护实现即可。

话不多说,代码搞起来:

先来一个可以待优化的实例:

定义一个抽象接口:

  1. 只要实现一个方法 getTemplateData 获取模板对应返回的数据即可。
// TemplateData ...
type TemplateData interface {
	getTemplateData(data []string) (map[string]interface{}, error)
}
  1. 写出具体你要定义的类,对应到底有哪些情况对应的模板输出。
// PayOk 1支付成功
type PayOk struct{}

func (p PayOk) getTemplateData(data []string) (map[string]interface{}, error) {
	return map[string]interface{}{
		"first": map[string]interface{}{
			"value": "尊敬的用户,您的订单已支付成功,我们会尽快为您发货。",
			"color": Color,
		},
		"keyword2": map[string]interface{}{
			"value": data[1],
			"color": Color,
		},
		"keyword1": map[string]interface{}{
			"value": data[0],
			"color": Color,
		},

		"remark": map[string]interface{}{
			"value": "请耐心等待收货,收到货后记得回来确认哦。",
			"color": Color,
		},
	}, nil
}

// AutoCancelOrder 2自动取消订单
type AutoCancelOrder struct{}

func (a AutoCancelOrder) getTemplateData(data []string) (map[string]interface{}, error) {
	return map[string]interface{}{
		"first": map[string]interface{}{
			"value": "尊敬的用户,您的订单未在指定时间内支付,已自动取消。",
			"color": Color,
		},
		"keyword2": map[string]interface{}{
			"value": data[1],
			"color": Color,
		},
		"keyword1": map[string]interface{}{
			"value": data[0],
			"color": Color,
		},

		"remark": map[string]interface{}{
			"value": "感谢您的购买。",
			"color": Color,
		},
	}, nil
}

// OrderDelivery 3订单发货
type OrderDelivery struct{}

func (o OrderDelivery) getTemplateData(data []string) (map[string]interface{}, error) {
	return map[string]interface{}{
		"first": map[string]interface{}{
			"value": "尊敬的用户,您的订单已发货,正快马加鞭向您飞奔而去。",
			"color": Color,
		},
		"keyword4": map[string]interface{}{
			"value": data[3],
			"color": Color,
		},
		"keyword1": map[string]interface{}{
			"value": data[0],
			"color": Color,
		},
		"keyword2": map[string]interface{}{
			"value": data[1],
			"color": Color,
		},
		"keyword3": map[string]interface{}{
			"value": data[2],
			"color": Color,
		},

		"remark": map[string]interface{}{
			"value": "请前往订单详情,查看物流详情。",
			"color": Color,
		},
	}, nil
}

// OrderAutoTake 4订单自动收货
type OrderAutoTake struct{}

func (o OrderAutoTake) getTemplateData(data []string) (map[string]interface{}, error) {
	return map[string]interface{}{
		"first": map[string]interface{}{
			"value": "尊敬的客户,您的订单已自动收货:",
			"color": Color,
		},
		"keyword3": map[string]interface{}{
			"value": data[2],
			"color": Color,
		},
		"keyword1": map[string]interface{}{
			"value": data[0],
			"color": Color,
		},
		"keyword2": map[string]interface{}{
			"value": data[1],
			"color": Color,
		},

		"remark": map[string]interface{}{
			"value": "祝您购物愉快。",
			"color": Color,
		},
	}, nil
}

这里暂时给出三个模板即可,可能有20个或者40个不等。

  1. 最后封装一个上下文控制工厂函数,用户调用者根据具体协商的参数进行调用对应的算法即可。
func FactoryTemplate(tagID string, data []string) (map[string]interface{}, error) {
	switch tagID {
	case "1":
		return DoTemplate(new(PayOk), data)
	case "2":
		return DoTemplate(new(AutoCancelOrder), data)
	case "3":
		return DoTemplate(new(OrderDelivery), data)
	default:
		return map[string]interface{}{}, errors.New("no found the tagID: " + tagID)
	}
}

如上代码,给出的是用一个switch给出工厂选出具体算法,可是一旦模板多了也不好维护,代码也不够优雅。

下面我们利用 单例模式 和 简单工厂模式构造一个map结构优化该方法.

4. 优化 FactoryTemplate 函数

var doTep sync.Once
var Template = make(map[string]TemplateData)

// FactoryTemplate ...
// tagID: 模板ID
// data: 对应模板参数keyword
func FactoryTemplate(tagID string, data []string) (map[string]interface{}, error)  {
	doTep.Do(func() {
		Template["1"]= new(PayOk)
		Template["2"]= new(AutoCancelOrder)
		Template["3"]= new(OrderDelivery)
	})
	
	if _,ok :=Template[tagID]; !ok {
		return nil, errors.New("tagID invalid")
	}
	
	return Template[tagID].getTemplateData(data)
}

总结

优点:

  1. 策略模式符合开闭原则
  2. 避免使用多重条件语句, if…else; switch…case语句
  3. 使用策略模式可以提高算法的保密性和安全性

缺点:

  1. 调用者必须提前约定好对应的调用条件,并自行决定使用哪一个策略类算法。
  2. 代码在一开始写的时候会比较多,在写的时候可能会加大工作量,但是在后续维护添加时简洁清晰、一目了然。

对应设计模式,还有很多精妙之处,特别是在很多优秀的开源框架中大量利用设计模式解决较为复杂的问题,适当的利用合适的设计模式有利于项目的完整安全性。

当然对于golang来说也应该适当轻量化面向对象的设计方式,提倡组合的方式。