利用map实现策略模式

2023-06-16 08:54:21 浏览数 (1)

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实现策略模式

0 人点赞