Golang语言情怀-第19期 Go 语言设计模式-适配器

2021-01-25 10:39:11 浏览数 (2)

适配器模式介绍

说起适配器模式,相信很多做android的同学第一印象就是AdapterView的Adapter,那它是干嘛用的呢?为什么要叫adapter呢?要了解这个问题,我们首先来看看适配器模式的定义:

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。——Gang of Four

恩,看起来好像有点迷糊,举个例子吧:

我电脑的电源是三个插头(也就是有地线)的那种,不知道为啥学校的插座都是两个插孔的,哎呀,这可咋办啊!同学建议我们买个转换器,这种转换器有三个插孔,我的电源可以插进去,同时它还有两个插头,可以插进学校的插座里,嘿嘿,同学真聪明,这么容易的就解决了我的问题。

在上面的例子里,那个转换器也可以叫做适配器,我们现在要说的适配器模式灵感就是来自上述所述的实际生活中遇到的问题。那在我们程序设计中会遇到什么样的问题呢?再来看个例子:

应老师的要求,我们现在需要做一个音乐播放器,现在我算是知道一点面向对象的原则了,所以我首先设计了一个接口,这个接口有一个playMusic的方法,接着我很轻松的利用这个接口设计出了一个音乐播放器,音乐控制器通过调用playMusic可以完美的播放任何音乐,啧啧啧,高兴中…老师对我的音乐播放器也很满意,不过他又提出了新的需求,让我的音乐播放器也可以播放游戏的声音,并给了我一个播放游戏声音的类,这个类也很简单,只有一个playSound方法,虽然很简单,但是现在我困惑了,因为我设计的音乐控制器只认识playMusic而不认识playSound,难道我要重新设计我的音乐控制器吗?正当我苦恼的时候,同学出现在了我身后,轻声的告诉我:“适配器模式可以完美的解决你的问题,你只需要写一个Adapter实现你的音乐播放接口,在这个Adapter的playMusic中去调用游戏声音播放器的playSound方法就可以了。”听了同学的话,我突然恍然大悟,原来这就是适配器模式!

好了,通过上面的三个小段子,相信大家对适配器模式应该了有了大概的认识,下面还是用一张结构图来清晰的描述一下什么是适配器模式吧。

通过上面的图我们也可以看出来,适配器要做的事情就是让我们写的野实现适配到系统需要的标准实现上。下面我们迅速进去代码模式,让代码告诉我们适配器模式张啥样!

代码实现

代码实现环节,我们还是用上面那个音乐播放器的例子,首先设计一个音乐播放的接口:

代码语言:javascript复制
package player

type Player interface {
    PlayMusic()
}

这个接口只有一个方法PlayMusic,系统通过调用PlayMusic这个方法达到播放音乐的目的。在来看看我们播放音乐的实现。

代码语言:javascript复制
package main
import . "./player"

func main() {
    var player Player = MusicPlayer {Src:"music.mp3"}
    play(player)
}

func play(player Player) {
    player.PlayMusic()
}

代码也超级简单,一个play方法去调用了Player的实现的PlayMusic方法。来看看结果,

现在我们的音乐播放器可以播放歌曲了,只需要给出一个歌曲的路径就ok,不过现在我们还需要播放游戏声音,并且给了我们一个这样的实现。

代码语言:javascript复制
package player
import "fmt"

type GameSoundPlayer struct {
    Src string
}

func (p GameSoundPlayer) PlaySound() {
    fmt.Println("play sound: "   p.Src)
}

GameSoundPlayer也是有一个Src属性,也有一个方法,不过这个方法叫PlaySound,并不是我们需要的PlayMusic,那可咋办呢?别忘了咱们的play方法 需要的是一个Player的实现,并自动调用了PlayMusic方法,下面本节的主角-GameSoundAdapter出场。

代码语言:javascript复制
package player

type GameSoundAdapter struct {
    SoundPlayer GameSoundPlayer
}

func (p GameSoundAdapter) PlayMusic() {
    p.SoundPlayer.PlaySound()
}

GameSoundAdapter有一个GameSoundPlayer类型的属性,它就是我们上面的那个游戏声音播放器,GameSoundPlayer还有一个方法名字叫PlayMusic,所以GameSoundPlayer实现了Player接口,我们可以把它用于player方法中,在PlayMusic中我们是调用的GameSoundPlayerPlaySound来播放声音的。 来看看我们这个适配器适配的咋样,

代码语言:javascript复制
package main
import . "./player"

func main() {
    gameSound := GameSoundPlayer {Src:"game.mid"}
    gameAdapter := GameSoundAdapter {SoundPlayer:gameSound}
    play(gameAdapter)
}

func play(player Player) {
    player.PlayMusic()
}

看main函数中,首先我们还是有一个GameSoundPlayer类型的变量,然后将它赋值给了GameSoundAdapterSoundPlayer属性,下面调用GameSoundAdapterPlayMusic方法,就可以间接的调用GameSoundPlayerPlaySound方法了,这样我们就轻松的将GameSoundPlayer适配到了Player。 来看看结果:

整体来看我们的代码还是很简单,不过简单的代码已经将适配器模式讲解的很清楚了,那最后我们来思考一个问题,适配器模式体现了哪些面向对象的设计原则呢?针对接口编程有木有? 开闭原则有木有?

再举个例子:

代码语言:javascript复制
package main

import (
    "fmt"
)

func printName(firstName, secondName string) {
    fmt.Printf("firstName is %s, secondName is %sn", firstName, secondName)
}

func main() {
    printName("Jiajun", "Huang")
}

看其中的 printName 函数的签名 (firstName, secondName string),打印结果就是:

代码语言:javascript复制
$ go build && ./test 
firstName is Jiajun, secondName is Huang

但是不巧的是,这个项目现在被山西煤老板1一个亿收购了,煤老板是中国人,我们和外国人的名字不同之处,在于他们的名字在前, 姓氏在后,我们是姓氏在前,名字在后,所以老板使用这套系统的时候很不开心:

代码语言:javascript复制
$ go build && ./test 
firstName is 三, secondName is 张

怎么叫三张呢?应该叫张三才对!因此我们需要把接口换一下,伟大的架构师决定重构!毕竟老板特别生气,但是老系统还有很多 老外的资料在跑,虽然所有中国人都用中国人的输出方式,但是还是希望不要把老外的也统一过来,要不然客户又要生气了。

所以架构师决定,用一个新的函数来替代以前的这个 printName,并且能够实现,中国人用中国人的方式打印,外国人用外国人的, 并且要提供扩展性:

代码语言:javascript复制
package main

import (
    "fmt"
)

func isChinese(string) bool {
    // 略
    return true
}

func isEnglish(string) bool {
    // 略
    return true
}

func printEnglishName(firstName, secondName string) {
    fmt.Printf("firstName is %s, secondName is %sn", firstName, secondName)
}

func printChineseName(familyName, name string) {
    fmt.Printf("姓: %s,名: %s", familyName, name)
}

func printName(familyName, name string) {
    if isChinese(familyName) {
        printEnglishName(name, familyName)
    } else if isEnglish(familyName) {
        printChineseName(familyName, name)
    } else {
        fmt.Println("暂不支持/Not support yet")
    }
}

func main() {
    // 略
}

如此,便成功的实现老板的需求,并且可以遇见,下次项目再被法国人一亿欧元买回去的时候,他们仍然可以愉快的使用这个系统。

这就是适配器的作用,对接多个端,如果你想不起什么是适配器的话,想象一下我的港版手机就知道了 :)

参考资料:

Go语言设计模式-适配器

https://studygolang.com/articles/5895

Go语言设计模式-适配器

https://zhuanlan.zhihu.com/p/166364492

0 人点赞