1. 代码实现
手上有一个工作,要做一个数据库类型转换工具,比如MySQL转ClickHouse,那么这个工具大部分的工作就是在做映射关系的比对。
最初写的代码大概是这个样子的:
代码语言:go复制switch (mysqlType) {
case MySQLInt:
if column.IsUnsigned {
col.Type = UInt32
}
col.Type = UInt32
// 省略大部分逻辑
}
这样的代码可想而知,会有很多个case分支,存在下面几个问题:
- 代码丑陋,大量的逻辑放在一个代码块中,极为丑陋难以阅读理解
- 难于扩展,每次都需要在这个代码块中增删
而这种映射关系最好的保存结构一定是map,所以我用map做了一定的改造。首先,引入了一个struct类型,来保存column的信息:
代码语言:go复制type myType struct {
colType ColumnType
isUnsigned bool
}
接下来改造map:
代码语言:go复制var dict1 = map[myType]ColumnType {
myType{colType: MySQLInt, isUnsigned: false}:Int32,
myType{colType: MySQLInt, isUnsigned: true}:UInt32,
}
这样一来,逻辑代码就变得很简约了,一行代码就能搞定逻辑判断:
代码语言:go复制col.Type = dict1[myType{colType:mysqlType, isUnsigned: column.IsUnsigned}]
不过类型里还有一类硬茬,就是带精度的类型,如Decimal(m, n),处理的时候还需要把精度拿出来,然后构造新的类型,代码如下:
代码语言:go复制switch (mysqlType) {
case MySQLDecimal:
col.Type = ColumnType(fmt.Sprintf("%s%s", Decimal, column.Precision))
// 省略大部分逻辑
}
这就很恼火了,我不能无限制的改造struct用来适配,代码会变得很难看和难以维护,而且冗余的东西太多。所以我想到了用函数类型去做这个事情:
代码语言:go复制type transformer func(column Column) ColumnType
利用这个函数类型对Decimal这种进行改造,最后的效果是这样的:
代码语言:go复制type transformer func(column Column) ColumnType
var dict2 = map[ColumnType]transformer{
MySQLInt: func(col Column) ColumnType {
if col.isUnsigned {
return UInt32
}
return Int32
},
MySQLDecimal: func(col Column) ColumnType {
return ColumnType(fmt.Sprintf("%s%s", ChDecimal, column.Precision))
}
}
这样的几次改造后,我的代码里终于消灭了所有的switch-case。回过头来看,这次改造还是一次策略模式的应用,目的就是利用策略模式消灭掉if-else,或者说过多的if-else。
而这次改造后的代码,无论后面加类型还是删除类型,都不需要改动业务逻辑代码,只要在dict2里增加就可以了,基本符合开闭原则。
本来学习GoF的设计模式的时候,总是拘泥于面向对象的实现,每次还得先画类图,总之改造起来很累。
后来发现Spring框架里可以轻松的用map实现,就开始感觉其实策略模式的实现不需要拘泥于一种方式,只要思路对,那一定可以实现。
2. 参考资料
用SpringBoot实现策略模式