设计模式本来是开发人员多年来总结的一套问题的解决方案。早期在从事C/C++开发之初时,了解过各种设计模式的思想,以及其实现的代码。接下来我将结合着设计模式的讲解,并将其用Go代码实现。总结一套Go语言设计模式代码片段,读者可理解后举一反三的应用到自己的开发实践中。
设计模式包括创建型模式、结构型模式、行为型模式。本节我们将从创建型模式开始讲解。
工厂模式
我们来看一个实际的应用场景,假设你刚开始运营一家快递公司,使用货运汽车进行快递运输。你的代码或许如下:
type Car struct {
}
func NewCar() *Car {
return &Car{}
}
此时,由于你的公司发展迅速赚了不老些钱,你打算开辟航运线路进行快递运输,提升你企业在快递行业的竞争力。这个时候该怎么做呢?
我先来说说反例,很多公司早期发展迅速,为了尽快满足业务需求,没有充分去设计代码的实现,或许在Car结构里面增加一些航运的方法,在Newcar里面增加一些逻辑判断返回满足航运的对象。后期公司又要发展海运等,那这个代码就越来越不好维护了。
这里只是假设了一个简单的场景,但是在我们实际生成中的业务会比这复杂得多,当这样的代码越来越多了,自然整个系统的维护成本越来大。互联网行业本来就人员流动频繁,当接手该项目的新人在对业务不足够了解时,又需要快速满足业务,往往这个时候就埋下了雷。当系统埋的雷越来越多,维护人员流动也就越频繁,导致恶性循环。而大家都知道这个问题的存在,也不会投入多少资源让你去对整体代码甚至整个业务线进行梳理。因为业务是可以赚钱的,这个我们作为程序员也要理解。
以上这段本来与本节讲解的内容关系不大,但是我还是尽力的控制自己的情绪吐槽下。目前我从事的岗位正是这样,真的头大。
回到正题,那我们应该怎样去解决上面说到的增加航运途径的代码逻辑呢?这里就用到了工厂模式,其代码如下:
// 运输方式
type Delivery interface {
DeliveryPackage() error
}
// 汽车
type Car struct {
}
func (c Car) DeliveryPackage() error {
return nil
}
// 飞机
type Plane struct {
}
func (p Plane) DeliveryPackage() error {
return nil
}
func New() *Delivery {
return ...
}
其他语言实现工厂模式都是通过类的继承来实现的,但是Go语言由于没有继承。我们利用 interface 模拟 Car 、Plane 继承至 Delivery来实现。工厂模式中,业务方(也就是调用New方法的业务)无需关注是空运还是货运。其只管调用 DeliverPackage() 将货发送出去。而未来若增加了航运模式,类似的只用增加船运方式的类(Go中的struct),而对货运航运的业务没有任何干扰。
通过以上实现可以很显然的看出工厂模式的解决方案比讲述的反例代码维护上好得多。
讲到这里我仍然还想对工作中遇到的实际案例说说自己的见解。我认为在一个函数内需要谨慎多次、嵌套使用 if 判断语句。不仅是开发者需要注意,进行 Code Review 的代码审核者也得严格把控。因为代码中的if嵌套太多,后续对代码的理解、业务的理解真的太难绕出来了。
来讲一个实际的案例,我目前负责的国际电商相关的业务,而不同的地区有些业务规则或者业务功能就不一样,在一个接口里的函数全都是各种 if 对地区的判断,有些地区某些功能是一样的就会出现 if 里面或关系。
当层层嵌套后,对于理解某个地区的业务确实很难进行。在某个时候,可能有需求对印度做一个什么新功能。这个时候就太难了,在原来的基础上改,很可能会影响其他的地区。就算是不影响其他地区,测试也得全地区覆盖验证到,这样反倒影响了业务开展时间。
我认为每个地区就抽象出自己的一个类,每个地区类处理该地区的逻辑,虽然某些地区的逻辑可能一致,也可以使用一些抽象出公共方法的处理方式来解决,甚至就复制粘贴代码,我认为都比所有的地区柔和在一起更好维护。
再回到工厂模式,我们可以把每个地区认为是工厂模式生成的对象,在New的时候传入地区参数就行,后续的逻辑就在对应地区的成员方法里了。例如工厂模式代码。
我们来看下如果没有使用工厂模式,就像我说的所有的地区柔和在一起代码是怎样的呢?
func DeliveryPackage(deliveryType string) error {
if deliveryType == "car" {
// ...
}else if deliveryType == "plane" {
// ...
} else if deliveryType == "ship" {
// ...
}else {
return errors.New("invalied delivery type")
}
// ...
if deliveryType == "car" || deliveryType == "ship" {
// ....
if ...
}
// ....
if deliveryType == "car" || deliveryType == "plane" {
// ....
}
// ...
}
这样不仅每个方法需要处理对deliveryType的判断,同时你认为你方便读出 car 类型的或许方式需要经过哪些处理吗?又或者是新来一个业务需要对 plane类型做修改,能够保证不影响其他的业务逻辑吗?
这一节讲解的模式比较少,可以说是大篇幅都在讲错误的代码案例,这也是给读者传达使用设计模式的重要性,当我们的代码每一行、每一个函数、每一个类都是经过揣摩出来的,而不是一味的任性赶快。这样才能使我们进步,同时也使整个项目更好的维护。设计模式正是是给我们的揣摩带来灵感。
往期回顾:
go语言系列4 - Goroutine、channel的使用还有话说
go语言系列7 - Go defer优化之open_coded