设计模式:程序员跳不开的坑
学编程,总是逃不了要学“算法”,也总跳不开要学“设计模式”。
无论学习什么语言,设计模式始终是我们必须掌握的,这是程序员的基本功。
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。
项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
今天给大家推荐的新课《Go 语言实现 23 种设计模式》,教程涉及7大设计原则、23种设计模式,每种设计模式都从定义、应用及Golang实例三个部分进行详细介绍。
本课需要学员对 Go 语言有一定的基础哦,可先学习《Go 语言简明教程》之后再来学习本课。
本课程概念较多,受篇幅限制,不宜放过多文字,下面截取课程第一章内容给大家介绍第1种设计原则——开闭原则的概念,想要学习完整设计原则及设计模式的小伙伴请移步至《Go 语言实现 23 种设计模式》学习整个课程。
开闭原则
开闭原则(Open Closed Principle,OCP) 由勃兰特.梅耶(Bertrand Meyer)提出,他在 1988 年的著作《面向对象软件构造》( Object Oriented Software Construction)中提出:软件实体应当对扩展开放,对修改关闭(Software entities should be open for extension, but closed for modification),这就是开闭原则的经典定义。
开闭原则是设计模式中的总原则,开闭原则就是说:对拓展开放、对修改关闭。模块应该在尽可能不修改代码的前提下进行拓展,这就需要使用接口和抽象类来实现预期效果。我们举例说明什么是开闭原则,以 4s 店销售汽车为例,其类图如图所示:
ICar 接口定义了汽车的两个属性:名称和价格。BenzCar 是一个奔驰车的实现类,代表奔驰车的总称。Shop4S 代表售卖的 4S 店,ICar 接口的代码清单如下:
<pre> package main
import "fmt"
type ICar interface {
// 车名
GetName() string
// 价格
GetPrice() int
}
copy</pre>
一般情况下 4S 店只出售一种品牌的车,这里用奔驰为例,代码清单如下
<pre> type BenzCar struct {
name string
price int
}
func (b BenzCar) GetName() string {
return b.name
}
func (b BenzCar) GetPrice() int {
return b.price
}
copy</pre>
这里我们模拟一下 4s 店售车记录:
<pre> func main() {
var (
list []ICar
)
list = []ICar{}
list = append(list,&BenzCar{"迈巴赫",130})
list = append(list,&BenzCar{"AMG",343})
list = append(list,&BenzCar{"V",60})
for _,v := range list {
fmt.Println("车名:",v.GetName(),"\t价格:",v.GetPrice())
}
}
copy</pre>
接下来,我们在命令行中输入 cd Principle 先切换到 go 文件所在目录下,然后执行 go run 1.go 来看我们的执行结果。如下图所示:
暂时来看,以上设计是没有啥问题的。但是,某一天,4s 店老板说奔驰轿车统一要收取一笔金融服务费,收取规则是价格在 100 万元以上的收取 5%,50~100 万元的收取 2%,其余不收取。为了应对这种需求变化,之前的设计又该如何呢?
目前,解决方案大致有如下三种:
- 修改 ICar 接口:在 ICar 接口上加一个 getPriceWithFinance 接口,专门获取加上金融服务费后的价格信息。这样的后果是,实现类 BenzCar 也要修改,业务类 Shop4S 也要做相应调整。ICar 接口一般应该是足够稳定的,不应频繁修改,否则就失去了接口锲约性了。
- 修改 BenzCar 实现类:直接修改 BenzCar 类的 getPrice 方法,添加金融服务费的处理。这样的一个直接后果就是,之前依赖 getPrice 的业务模块的业务逻辑就发生了改变了,price 也不是之前的 price 了。
- 使用子类拓展来实现:增加子类 FinanceBenzCar,覆写父类 BenzCar 的 getPrice 方法,实现金融服务费相关逻辑处理。这样的好处是:只需要调整 Shop4S 中的静态模块区中的代码,main 中的逻辑是不用做很大的修改的。
新增的 FinanceBenzCar 类代码清单如下:
<pre> type FinanceBenzCar struct {
BenzCar
}
func (b FinanceBenzCar) GetPrice() int {
// 获取原价
selfPrice := b.price
var finance int
if selfPrice >= 100 {
finance = selfPrice + selfPrice5/100 } else if selfPrice >= 50 { finance = selfPrice + selfPrice2/100
} else {
finance = selfPrice
}
return finance
}
copy</pre>
主函数:
<pre> func main() {
var (
list []ICar
)
list = []ICar{}
list = append(list,&FinanceBenzCar{BenzCar{"迈巴赫",99}})
list = append(list,&FinanceBenzCar{BenzCar{"AMG",200}})
list = append(list,&FinanceBenzCar{BenzCar{"V",40}})
for _,v := range list {
fmt.Println("车名:",v.GetName(),"\t价格:",v.GetPrice())
}
}
copy</pre>
测试结果
<pre> === RUN TestBenzCar_GetName
车名: 迈巴赫 价格: 100
车名: AMG 价格: 210
车名: V 价格: 40
--- PASS: TestBenzCar_GetName (0.00s)
PASS
copy</pre>
这样,在业务规则发生改变的情况下,我们通过拓展子类及修改持久层(高层次模块)便足以应对多变的需求。开闭原则要求我们尽可能通过拓展来实现变化,尽可能少地改变已有模块,特别是底层模块。
开闭原则总结:
- 提高代码复用性
- 提高代码的可维护性
本课不像其他项目实战课程那般,相对晦涩难懂,不那么富有趣味性,但任何一个有趣、有用的程序、项目或者游戏,都需要坚实的基本功方能实现,设计模式就是我们必学的基本功。
“每一栋大厦,都从一块砖开始”