我们接着看行为模式中的模板方法模式。
模板方法模式为定义了一系列的行为步骤规范(方法、函数),而在一些特殊的场景下可能不同的步骤有着不同的处理方法需要特别的实现。
我们去超市买东西有寻找商品、查看价格、支付以下步骤。而在查看商品价格这一步,大多数商品可以直接看货柜上的价格,而水果、素菜只能看到单价,如果需要知道实际的价格还得进行称重。
我们的代码逻辑中处理查看价格或许直接使用 if 判断了,如果是水果则还需要进行计算,例如代码:
func (g *Goods) getPrice() float32 {
if g.goodsType == GoodsTypeFruit {
return g.price * g.weight
}
return g.price
}
当不同计价商品的种类越来越多,这里的 if 也将越来越长,此时代码的阅读性、可维护性越来越差。
我们来看下模版方法模式是如何处理上述过程的。
const (
GoodsTypeNormal = 0
GoodsTypeFruit = 1
)
type Goods struct {
name string
price float32
weight float32
}
type calcu interface {
searchGoods(string) Goods
getPrice(Goods) float32
pay(float32)
}
type Normal struct {
}
func (Normal) searchGoods(name string) Goods {
return Goods{name, 0, 0}
}
func (Normal) getPrice(g Goods) float32 {
return g.price
}
func (Normal) pay(price float32) {
fmt.Printf("已支付 %f 元\n", price)
}
type Fruit struct {
Normal
}
func (f Fruit) searchGoods(name string) Goods {
return f.Normal.searchGoods(name)
}
func (Fruit) getPrice(g Goods) float32 {
return g.price * g.weight
}
func (f Fruit) pay(price float32) {
f.Normal.pay(price)
}
我们可以看到这里Normal为模板, Fruit 除了自己实现了 getPrice方法,其余方法都是使用的模版类提供的方法,避免如反例中使用过长的if。
我们回过头来看下所有的行为模式,正如我们在设计模式第一讲中传达的观点,尽量避免使用过多的if。行为模式的最基本思想是把不同判断下的逻辑处理放到不同的类中去实现。
当我们发现代码逻辑中的ifelse、switch层级过多,过长,这个时候我们需要根据实际场景来选择相应的行为模式完成代码实现,这样代码更美观且具有高可维护性。
但是在我们的实际开发中,当我们又新增了某种场景时,我们总是在以下两个选择中徘徊:
避免对原来的代码修改而产生问题,我也往后面加个else if;
有代码洁癖性,把以前的if套用某个设计模式重新实现,并增加一个场景。
我在工作之初大多数情况下会选择第二项。但是也为此付出过惨痛的代价,由于对业务没有十足的掌握,老的代码逻辑未完全梳理清楚而做大的改动而产生线上事故。
随着工作年限的增长有一种思想可能会更多的在脑海里占据优势,在业务驱动的公司,业务才是赚钱的,你的代码只是实现赚钱的工具而已。你把代码写得再优秀,也没法把价值体现出来。但是一旦出现了事故,会让你之前所有的付出打了水漂。当出现过事故,尝到疼痛感后,在下一次出现同样的情形时,大多数人可能会选择第一项了,大量的去改前人的代码有些得不偿失。
以上只是自己工作的感悟,相信大多数读者也有同样的困扰。也畅想希望设计模式能够深入每一位开发人员的脑海里,大家写的代码都很优秀,就不存在难以抉择的囧境了。
有关设计模式的讲解就到这里了,这一系列并没有把所有的设计模式一一列举出来,有少许设计模式通过Go代码实现与其他的模式类似,就没有再阐述了,不了解的读者,可自行阅读设计模式相关教程。
下图为完整的设计模式及其分类:
从下一节开始,我们将开启容器系列教程。
近期回顾: