if else这样的控制流几乎在每个编程语言中都会存在。在go里面也不例外。但是如果代码里面充斥着许多的if else,那么对于阅读代码的人很难清除代码表达的的意思,特别是在if else里面嵌套很多层的if else。如下:
if condition {
if condition2 {
if condition4{
if condition6{
//...
}
} else condtion5{
}
} else condition3 {
}
}
比如定义了一个函数,这个函数接收一个价格参数,根据不同的用户等级,有不同的打折方案。
首先我们想到的代码可能是如下:
func discount(price float64) string {
// 用户是超级vip
if superVip {
return price*0.7
}
//钻石vip
if diamondVip {
return price*0.8
}
//黄金vip
if goldenVip {
return price*0.9
}
}
第一个:使用策略模式+map
//定义一个策略接口
type PriceStrategy interface {
// 根据用户等级获取价格
discount(price float64) float64
}
// 定义j结构体来一一实现接口
type SuperVipPriceStrategy struct{}
// 实现接口 只处理用户是超级vip的事情
func (g SuperVipPriceStrategy ) discount(price float64) string {
return price*0.7
}
type DiamondVipPriceStrategy struct{}
// 实现接口 只处理用户是超级vip的事情
func (g DiamondVipPriceStrategy ) discount(price float64) string {
return price*0.8
}
//...其他等级的实现是类似
//准备一个map存放所有额策略
var strategyMap = make[string]PriceStrategy
func init(){
//初始化所有的策略
strategyMap["superVip"] = &SuperVipPriceStrategy{}
strategyMap["diamond"] = &DiamondVipPriceStrategy{}
//...
}
type User struct {
Id int
//等级
level string
//...
}
func main(){
var orderPrice = 99.1
//准备一个用户,世纪业务应该是获取当前登录用户信息
u := &User{
Id:1,
Level: "superVip",
}
g := strategyMap[u.Level]
g.discount(orderPrice)
}
为了方便管理所有的策略实现,可以定义一个用来管理策略的专属上下文Context
type PriceStrategyContext struct {
//存放策略的map
m map[string]PriceStrategy
}
func NewPriceStrategyContext(m map[string]PriceStrategy) *PriceStrategyContext {
return &PriceStrategyContext{
m: m,
}
}
//定义一个添加新的策略的方法
func (c PriceStrategyContext)register(pg PriceStrategy,name string){
c.m[name] = pg
}
//根据名称获取一个策略
func (c PriceStrategyContext)get(name string) PriceStrategy {
return c.m[name]
}
另一种Context
type PriceStrategyContext struct {
PriceStrategy
}
//设置一个策略
func (c *PriceStrategyContext)SetPriceStrategy(riceStrategy PriceStrategy) {
c.priceStrategy=priceStrategy
}
//执行策略
func (c *PriceStrategyContext) exec(price float64) float64{
return c.priceStrategy.discount(price)
}
第二个:表驱动
这个方法是把策略先保存到数据库表里面,通过数据库表的方式来存取。
比如数据库表是这样的:id(主键),name(策略的名称),impl(策略的实现),当查询出来之后,通过反射创建策略的对象。
/根据 type 创建对象
func NewObject(source any) any {
t := releft.TypeOf(source)
return reflect.New(t)
}
第三个:不需要特别处理的提前返回
if condition1 {
if condition2{
//...
}
}
修改为
if !condition1 {
return
}
if !condition2{
return
}
//... 真正需要处理的逻辑
第四个:责任链模式
比如业务里面有许多的校验规则,当然简单的可以使用validator打tag的方式进行校验。
但是有时候随着业务的复杂性增加,使用validator这种通用校验的方式变得不那么适用,可能每个业务的校验规则都不一样,那么每次都要进行很多if else的判断,会让代码量爆炸的,最后再来看山一样的代码心态都要崩了。
比如对一个订单信息进行校验,包含如下的几个校验:
价格的校验
订单个数的校验
商品总数的校验
会员等级的校验
活动时间的校验
一般的写法我想大家已经脑补出很多的if else了。但是责任链模式可以帮助你从无数多的if else里面解脱出来。责任链模式适合条件灵活多变的场景。
下面直接使用责任链的方式来实现复杂的校验:
//需要校验的信息
type OrderCheckConditions struct{
Price float64
OrderCount int
ProductCount int
UserLevel string
ActivityTime time.Time
}
//定义一个校验的通用接口
type OrderChecker interface{
Check(c *OrderCheckConditions) bool
}
//定义具体的校验
type PriceChecker struct{}
func (p *PriceChecker)Check(c *OrderCheckConditions) bool {
return c.Price>0
}
type OrderCountChecker struct{}
func (oc *OrderCountChecker)Check(c *OrderCheckConditions) bool {
return c.OrderCount>0 && c.OrderCount<=2
}
//...其他的校验类似
由于需要所有的校验都是true的时候,业务流程才可以往下面走,所以需要聚合所有的校验结果。
//责任链
func chain(c *OrderCheckConditions, checkers ...Checker) bool {
for _,checker := range checkers {
if !checker.check(c) {
return false
}
}
return true
}
第五个:使用assert断言
testify这个包里面定义了许多断言的方法,引入testify
go install -u github.com/stretchr/testify
使用
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEqual(t *testing.T) {
var a string = "Hello"
var b string = "Hello"
assert.Equal(t, a, b, "The two words should be the same.")
}
//或者这样
func TestEqual2(t *testing.T) {
// assertions 可以调用很多断言的方法
assertions := assert.New(t)
assertion.Equal(a, b, "")
// ...
}
第六个:optional(可选模式)
这个在java里面已经内置了Optional这个类型,go里面还没有支持这个类型,但是实现的思路是比较简单的。有兴趣的同学可以参考这个实现:
https://github.com/ThisIsClark/go-optional
在写函数时,也可以使用这样的思想
//获取某个配置的时候,如果是空的给默认值
someName := config.OptionalString("name", "def")