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


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

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

    设计模式(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(),"	价格:",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(),"	价格:",v.GetPrice())
        }
     }
     copy</pre>
    

    测试结果

    <pre> === RUN   TestBenzCar_GetName
     车名: 迈巴赫     价格: 100
     车名: AMG     价格: 210
     车名: V     价格: 40
     --- PASS: TestBenzCar_GetName (0.00s)
     PASS
     copy</pre>
    

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

    开闭原则总结:

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

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

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

  • 相关阅读:
    [WinAPI] API 13 [遍历指定目录 打印文件和其他属性]
    2014-3-12 星期三 小雨 [及时完成不堆积]
    2014-3-11 星期二 晴 [卓有成效 master 摸索计划方案]
    [ACM_几何] UVA 11300 Spreading the Wealth [分金币 左右给 最终相等 方程组 中位数]
    [ACM_水题] UVA 11729 Commando War [不可同时交代任务 可同时执行 最短完成全部时间 贪心]
    [ACM_水题] UVA 11292 Dragon of Loowater [勇士斗恶龙 双数组排序 贪心]
    [WinAPI] API 12 [获取程序所在的目录、程序模块路径,获取和设置当前目录]
    [WinAPI] API 11 [创建目录]
    [WinAPI] API 10 [创建、打开、读写文件,获取文件大小]
    java解析json数组
  • 原文地址:https://www.cnblogs.com/shiyanlou/p/12967293.html
Copyright © 2020-2023  润新知