为什么说「设计模式」是通往BAT的必经之路?

Posted 蓝桥云课精选

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么说「设计模式」是通往BAT的必经之路?相关的知识,希望对你有一定的参考价值。



世界上只有两种程序员:「懂设计模式的」和「不懂设计模式的」。
懂设计模 式的程序员,写出来的代码 优雅如诗,易读易维护,扩展性也更强。
不懂设计模式的程序员,代码随心所欲,但写完回头一看,往往自己都会一头雾水。随着代码量的上升,可维护性几乎为零。
设计模式(Design Pattern)是过去几十年的开发人员,经过长时间的试验和摸索,总结出来的一套程序设计标准,你可以将它理解为 程序员的「孙子兵法」或「三十六计」 几乎所有的大型软件、编程语言、框架都使用了这些标准。 学习和使用设计模式,可以:
1. 提高代码的可读性、可靠性、可复用性,使编程真正工程化;
2. 便于后期维护项目,增强系统的健壮性和扩展性;
3. 可以锻炼程序员的设计思维,提高代码质量。
当我们使用这一套成熟的框架和行动模式来走的时候,会发现很多事情处理起来变得轻松很多。 因此, 无论是在学习还是面试中,「设计模式」都是一个很重要的考点,是程序员的基本功之一。
首先来学习一下设计模式的七大设计原则,这些原则都是设计模式中最重要的部分,也是面试中经常被问到的。话不多说,以下是正文干货:
01

开闭原则
开闭原则(Open Closed Principle,OCP) 由勃兰特.梅耶(Bertrand Meyer)提出,他在 1988 年的著作《面向对象软件构造》( Object Oriented Software Construction)中提出:软件实体应当对扩展开放,对修改关闭(Software entities should be open for extension, but closed for modification),这就是开闭原则的经典定义。
开闭原则是设计模式中的总原则,开闭原则就是说:对拓展开放、对修改关闭。模块应该在尽可能不修改代码的前提下进行拓展,这就需要使用接口和抽象类来实现预期效果。
我们举例说明什么是开闭原则,以 4s 店销售汽车为例,其类图如图所示:
为什么说「设计模式」是通往BAT的必经之路?

ICar 接口定义了汽车的两个属性:名称和价格。BenzCar 是一个奔驰车的实现类,代表奔驰车的总称。Shop4S 代表售卖的 4S 店,ICar 接口的代码清单如下:
     
       
       
     
package main

import "fmt"

type ICar interface {
// 车名
GetName () string
// 价格
GetPrice () int
}
一般情况下 4S 店只出售一种品牌的车,这里用奔驰为例,代码清单如下
     
       
       
     
type BenzCar struct {
name string
price int
}

func ( b BenzCar ) GetName () string {
return b . name
}

func ( b BenzCar ) GetPrice () int {
return b . price
}
这里我们模拟一下 4s 店售车记录:
     
       
       
     
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 ())
}
}
接下来,我们在命令行中输入 cd Principle 先切换到 go 文件所在目录下,然后执行 go run 1.go 来看我们的执行结果。如下图所示:
为什么说「设计模式」是通往BAT的必经之路?
暂时来看,以上设计是没有啥问题的。但是,某一天,4s 店老板说奔驰轿车统一要收取一笔金融服务费,收取规则是价格在 100 万元以上的收取 5%,50~100 万元的收取 2%,其余不收取。为了应对这种需求变化,之前的设计又该如何呢?
目前,解决方案大致有如下三种:
  • 修改 ICar 接口:在 ICar 接口上加一个 getPriceWithFinance 接口,专门获取加上金融服务费后的价格信息。这样的后果是,实现类 BenzCar 也要修改,业务类 Shop4S 也要做相应调整。ICar 接口一般应该是足够稳定的,不应频繁修改,否则就失去了接口锲约性了。

  • 修改 BenzCar 实现类:直接修改 BenzCar 类的 getPrice 方法,添加金融服务费的处理。这样的一个直接后果就是,之前依赖 getPrice 的业务模块的业务逻辑就发生了改变了,price 也不是之前的 price 了。

  • 使用子类拓展来实现:增加子类 FinanceBenzCar,覆写父类 BenzCar 的 getPrice 方法,实现金融服务费相关逻辑处理。这样的好处是:只需要调整 Shop4S 中的静态模块区中的代码,main 中的逻辑是不用做很大的修改的。

新增的 FinanceBenzCar 类代码清单如下:
     
       
       
     
type FinanceBenzCar struct {
BenzCar
}

func ( b FinanceBenzCar ) GetPrice () int {
// 获取原价
selfPrice := b . price
var finance int
if selfPrice >= 100 {
finance = selfPrice + selfPrice * 5 / 100
} else if selfPrice >= 50 {
finance = selfPrice + selfPrice * 2 / 100
} else {
finance = selfPrice
}
return finance
}
主函数:
     
       
       
     
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 ())
}
}
测试结果
    
      
      
    
=== RUN TestBenzCar_GetName
车名: 迈巴赫 价格: 100
车名: AMG 价格: 210
车名: V 价格: 40
--- PASS: TestBenzCar_GetName (0.00s)
PASS
这样,在业务规则发生改变的情况下,我们通过拓展子类及修改持久层(高层次模块)便足以应对多变的需求。开闭原则要求我们尽可能通过拓展来实现变化,尽可能少地改变已有模块,特别是底层模块。
开闭原则总结:
  • 提高代码复用性

  • 提高代码的可维护性


02

单一职责原则
单一职责原则,简单来说就是保证设计类、接口、方法时做到功能单一,权责明确。比如:
为什么说「设计模式」是通往BAT的必经之路?
这里我们定义“更新用户” 的接口, 倘若有一天新来的前端要求加一个修改用户密码的接口,后端直接说:“你去调 updateUser”接口吧,传入密码信息就行。updateUser 接口的粒度太粗,接口职责不够单一,所以应该将接口拆分为各个细分接口,比如修改如下:
为什么说「设计模式」是通往BAT的必经之路?
这里很明显,我们看到分拆后的接口职责更加单一,权责更加清楚,日后维护开发也更加便捷。
单一职责原则,指的是一个类或者模块有只有一个改变的原因。 如果模块或类承担的职责过多,就等于这些职责耦合在一起, 这样一个模块的变快可能会削弱或抑制其它模块的能力, 这样的耦合是十分脆弱地。所以应该尽量保持单一职责原则,此原则的核心就是解耦和增强内聚性。
”我单纯,所以我快乐“用来形容单一职责原则再恰当不过了。
单职责原则总结:
  • 单一职责可以降低类的复杂性,提高代码可读性、可维护性。

  • 但是用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因目、环境而异;指责划分稍微不当,很容易造成资源浪费,代码量增多。

03

里式替换原则
里式替换原则的解释是,所有引用基类的地方必须能透明地使用其子类的对象。通俗来讲的话,就是说,只要父类能出现的地方子类就可以出现,并且使用子类替换掉父类的话,不会产性任何异常或错误,使用者可能根本就不需要知道是父类还是子类。反过来就不行了,有子类的地方不定能使用父类替换。
里式替换原则是开闭原则的实现基础,它告诉我们设计程序的时候尽可能使用基类进行对象的定义及引用,具体运行时再决定基类对应的具体子类型。
接下来举个例子,我们定义一个类 AbstractAnimal 对象,该对象声明内部方法” 跳舞”,其中,Rabbit、Dog、 Lion 分别继承该对象,另外声明一个 Person 类,该类负责喂养各种动物,Client 类负责逻辑调用,类图如下:
代码如下:
       
         
         
       
package main

import "fmt"

type Animal interface {
dance ()
}

type Rabbit struct {

}

func ( r Rabbit ) dance () {
fmt . Println ( "兔子跳舞" )
}

type Dog struct {

}

func ( d Dog ) dance () {
fmt . Println ( "狗跳舞" )
}

type Lion struct {

}

func ( l Lion ) dance () {
fmt . Println ( "狮子跳舞" )
}

type Person struct {
ani Animal
}

func ( p Person ) WalkAnimal () {
fmt . Println ( "人开始溜动物" )
p . ani . dance ()
}
主函数的调用如下
       
         
         
       
func main (){
person := Person { ani : & Dog {}}
person . WalkAnimal ()
}
我们尝试执行这段代码,你可以选择新建一个 go 文件,也可以在之前的代码中做修改,结果如下:
这里,Person 类中本该出现的父类 AbstractAnimal 我们运行时使用具体子类代替,只要是父类能出现的地方子类就能出现,这就要求我们模块设计时尽量以基类进行对象的定义及应用。
里氏替换原则总结:
  • 里氏替换可以提高代码复用性,子类继承父类时自然继承到了父类的属性和方法。

  • 提高代码可拓展性,子类通过实现父类方法进行功能拓展,个性化定制。

  • 里氏替换中的继承有侵入性。继承,就必然拥有父类的属性和方法。

  • 增加了代码的耦合性。父类方法或属性的更改,要考虑子类所引发的变更。

以上是关于为什么说「设计模式」是通往BAT的必经之路?的主要内容,如果未能解决你的问题,请参考以下文章

7年iOS开发,自述通往架构师的修炼之路精华篇

学会享受孤独是通往成功的必经之路

通往大神之路,百度Java面试题前200页。

解读通往8K/3D VR直播之路

Ruby语言工作坊学员分享:通往完全阅读之路

《Python机器学习及实践:从零开始通往Kaggle竞赛之路》