设计模式:程序员跳不开的坑

语言: CN / TW / HK

学编程,总是逃不了要学“算法”,也总跳不开要学“设计模式”。

无论学习什么语言,设计模式始终是我们必须掌握的,这是程序员的基本功。

设计模式(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%,其余不收取。为了应对这种需求变化,之前的设计又该如何呢?

目前,解决方案大致有如下三种:

  1. 修改 ICar 接口:在 ICar 接口上加一个 getPriceWithFinance 接口,专门获取加上金融服务费后的价格信息。这样的后果是,实现类 BenzCar 也要修改,业务类 Shop4S 也要做相应调整。ICar 接口一般应该是足够稳定的,不应频繁修改,否则就失去了接口锲约性了。
  2. 修改 BenzCar 实现类:直接修改 BenzCar 类的 getPrice 方法,添加金融服务费的处理。这样的一个直接后果就是,之前依赖 getPrice 的业务模块的业务逻辑就发生了改变了,price 也不是之前的 price 了。
  3. 使用子类拓展来实现:增加子类 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>

这样,在业务规则发生改变的情况下,我们通过拓展子类及修改持久层(高层次模块)便足以应对多变的需求。开闭原则要求我们尽可能通过拓展来实现变化,尽可能少地改变已有模块,特别是底层模块。

开闭原则总结:

  • 提高代码复用性
  • 提高代码的可维护性

本课不像其他项目实战课程那般,相对晦涩难懂,不那么富有趣味性,但任何一个有趣、有用的程序、项目或者游戏,都需要坚实的基本功方能实现,设计模式就是我们必学的基本功。

“每一栋大厦,都从一块砖开始”

欢迎关注我们的微信公众号,每天学习Go知识

分享到: