设计模式,大概意思就由码神们总结的、解决软件设计中反复出现的问题的、也可以说是一种编码规则

初始编码我们总是按照自己的套路来写代码,项目的代码量庞大了,需求又要改了,突然发现尼玛这个改起来好麻烦,好多文件要动,为了适合新的需求改动这个文件可能会对其他的业务逻辑产生影响,改动这个文件会不会引入bug?一系列的问题摆在了我们眼前,头疼、迷茫,有木有一种重新写一遍的冲动?

假如你遇到了这种情况,设计模式可能会拯救你一部分

工厂

简单工厂

简单工厂模式的工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的对象实例

单例

有很多写法,懒汉式、饿汉式、双重锁 … 目的是保证在一种特殊情况下的单例-并发

var m *Manager

func GetInstance() *Manager {
    if m == nil {
        m = &Manager{}
    }
    return m
}

type Manager struct{}

func (p Manager) Manage() {
    println("manage...")
}

先判断 m 为空才去构造, 保证第二次直接返回m,而不再重新构造,进而保证唯一实例


并发时可能,第一个goroutine执行到 m = &Manager {}前,第二个goroutine也来获取实例了

第二个goroutine去判断m是不是nil,因为m = &Manager{}还没有来得及执行,所以m肯定是nil

if中的语句可能会执行两遍!

单例失效了,此时就可考虑为单例加锁了

var m *Manager

var lock = &sync.Mutex{}

func GetInstance() *Manager {

    lock.Lock()
    defer lock.Unlock()

    if m == nil {
        m = &Manager{}
    }
    return m
}

type Manager struct {
}

func (p Manager) Manage() {
    println("manage...")
}

并发问题解决,但每次要加锁,性能不高

双重锁机制提高效率

func GetInstance() *Manager {

    if m == nil {
        lock.Lock()
        defer lock.Unlock()
        if m == nil {
            m = &Manager {}
        }
    }

    return m
}

用两个判断,将同步锁放在了条件判断之后,这避免每次调用都加锁,提高效率


自带 sync.Once 机制

var once sync.Once

func GetInstance() *Manager {
    once.Do(func() {
        m = &Manager{}
    })

    return m
}

策略

容易变动的代码从主逻辑中分离,通过接口规范它们的形式,主逻辑中将任务委托给策略

既减少了对主逻辑代码修改的可能性,也增加可扩展性。对扩展开发,对修改关闭设计原则

加减乘除法来模拟

package main

import (
    "fmt"
)

/**
 * 策略接口
 */
type Strategier interface {
    Compute(num1, num2 int) int
}

type Division struct{}

func (p Division) Compute(num1, num2 int) int {
    defer func() {
        if f := recover(); f != nil {
            fmt.Println(f)
            return
        }
    }()

    if num2 == 0 {
        panic("num2 must not be 0!")
    }

    return num1 / num2
}


func NewStrategy(t string) (res Strategier) {

    switch t {
    case "s": // 减法
        //
    case "m": // 乘法
        //
    case "d": // 除法
        res = Division{}
    case "a": // 加法
        fallthrough
    default:
        //
    }

    return
}


type Computer struct {
    Num1, Num2 int
    strate     Strategier
}

func (p *Computer) SetStrategy(strate Strategier) {
    p.strate = strate
}

func (p Computer) Do() int {
    defer func() {
        if f := recover(); f != nil {
            fmt.Println(f)
        }
    }()

    if p.strate == nil {
        panic("Strategier is null")
    }

    return p.strate.Compute(p.Num1, p.Num2)
}


func main() {

    com := Computer{Num1: 10, Num2: 5}
    strate := NewStrategy("d")

    com.SetStrategy(strate)
    fmt.Println(com.Do())
}

策略接口 Strategier ,实现的策略之 Division

工厂方法 NewStrategy ,根据不同type返回不同的策略

新策略,只需要在这个函数中增加对应的类型判断就ok

主流程 Computer,包括

  • 成员: 操作数 Num1,Num2
  • 成员: 策略接口
  • 方法: 设置策略的 SetStrategy

Do 中委托给了Strategier

适配器

将一个类的接口转换成希望的另外一个接口。使原本由于接口不兼容而不能一起工作的那些类可以一起工作

Player 接口方法 PlayMusic,MusicPlayer实现了

但 GameSoundPlayer 的方法叫 PlaySound,不是PlayMusic

func play(player Player) 方法需要的是一个Player的实现

通过 GameSoundAdapter 实现 Player接口,但实际由 GameSoundPlayer方法完成

package main

import (
    "fmt"
)

type Player interface {
    PlayMusic()
}

type MusicPlayer struct {
    Src string
}

func (p MusicPlayer) PlayMusic() {
    fmt.Println("play music: " + p.Src)
}

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

func main() {

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

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

}

type GameSoundPlayer struct {
    Src string
}

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

type GameSoundAdapter struct {
    SoundPlayer GameSoundPlayer
}

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

命令

几个角色:

  • Command: 命令
  • Invoker: 调用者
  • Receiver: 接受者
  • Client: 客户端

关系描述:

客户端通过调用者发送命令,命令调用接收者执行相应操作

调用者和接收者不知道对方的存在,之间是解耦合的

  • 遥控器 > 调用者
  • 电视 > 接收者
  • 命令 > 遥控器上的按键
  • 客户端 > 人

打开电视时,通过遥控器(调用者)的电源按钮(命令)来打开电视(接收者)

过程中遥控器是不知道电视的,但是电源按钮是知道他要控制谁的什么操作

package main

import (
    "fmt"
)

// receiver
type TV struct{}

func (p TV) Open() {
    fmt.Println("play...")
}

func (p TV) Close() {
    fmt.Println("stop...")
}

// command
type Command interface {
    Press()
}

type OpenCommand struct {
    tv TV
}

func (p OpenCommand) Press() {
    p.tv.Open()
}

type CloseCommand struct {
    tv TV
}

func (p CloseCommand) Press() {
    p.tv.Close()
}

// sender
type Invoker struct {
    cmd Command
}

func (p *Invoker) SetCommand(cmd Command) {
    p.cmd = cmd
}

func (p Invoker) Do() {
    p.cmd.Press()
}

func main() {

    var tv TV
    openCommand := OpenCommand{tv}
    invoker := Invoker{openCommand}
    invoker.Do()

    invoker.SetCommand(CloseCommand{tv})
    invoker.Do()

}

客户端(人)拿起遥控器,瞅准打开按钮(SetCommand),按该键(Do),按键被按下(Press),电视打开了(Open)

复合命令/宏命令,说白了就是将多个命令保存起来,通过遍历这个集合来分别调用各个命令

func NewOpenCloseCommand() *OpenCloseCommand {
    var openClose = &OpenCloseCommand{}
    openClose.cmds = make([]Command, 2)
    return openClose
}

type OpenCloseCommand struct {
    index int
    cmds  []Command
}

func (p *OpenCloseCommand) AddCommand(cmd Command) {
    p.cmds[p.index] = cmd
    p.index++
}

func (p OpenCloseCommand) Press() {
    for _, item := range p.cmds {
        item.Press()
    }
}


func main() {

    var tv TV
    var invoker Invoker

    var openClose  = NewOpenCloseCommand()
    openClose.AddCommand(OpenCommand{tv})
    openClose.AddCommand(CloseCommand{tv})
    invoker.SetCommand(openClose)
    invoker.Do()
}

命令模式具有很高的扩展性,遵循开闭原则,减少了修改代码引入bug的可能性

装饰者

new BufferedOutputStream(new FileOutputStream());

用另外一个类包装一下另外一个类, 或方便了使用, 或增强了功能

  • 理论上它们是可以无限包装的.
  • 装饰者和被装饰者们有相同的超类型(super).
  • 想要拓展功能无需修改原有的代码, 定义一个装饰者就可以

适配器在类型不匹配时用, 将一种类型伪装成另一种类型以便代码可以正常使用

装饰者和被装饰者拥有相同的类型(相同的超类),目的是为了增强功能或者方便使用

设计原则

从”包装”可以看到”多用组合,少用继承” 从”拓展”可以看到”开闭原则”

https://coolshell.cn/articles/17929.html

代理

抽象接口(Git)来规范代理和对象的行为(clone)

真实对象 Github 实现 Clone

通过 代理 GitBash 提供出来的 Clone,间接调用到 Git类型的 Clone

GetGit是提供者提供给我们的可调用的接口

假如要从GitLab上,只需要提供者添加一个GitLab对象, 然后调用GetGit时指定使用GitLab就可以了

package main

import (
    "fmt"
    "strings"
)
type Git interface {
    Clone(url string) bool
}

type GitHub struct{}

func (p GitHub) Clone(url string) bool {
    if strings.HasPrefix(url, "https") {
        fmt.Println("clone from " + url)
        return true
    }

    fmt.Println("failed to clone from " + url)
    return false
}

type GitBash struct {
    Gitcmd Git
}

func (p GitBash) Clone(url string) bool {
    return p.Gitcmd.Clone(url)
}

type Coder struct{}

func (p Coder) GetCode(url string) {
    gitBash := GetGit(1)
    if gitBash.Clone(url) {
        fmt.Println("success")
    } else {
        fmt.Println("failed")
    }
}

func GetGit(t int) Git {
    if t == 1 {
        return GitBash{Gitcmd: GitHub{}}
    }

    return nil // 可能还有其他的git源
}

func main() {
    var dd Coder
    dd.GetCode("https://github.com/zhangthe9/a.git")
    dd.GetCode("http://github.com/zhangthe9/a.git")
}

模板方法

模拟男女生出门时不同行为, 男生睡醒了爬起来就出去了; 女生要打扮一番才能出门

java版

定义一个接口, 规范行为

interface IPerson {
    void beforeOut();
    void out();
}

出门行为是一样的, 不一样的只是出门之前的行为

对应到代码中就是out相同, 不同的实现是beforeOut, 所以设计类时只关心out方法的实现

abstract class Person implements IPerson {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void out() {
        beforeOut();
        System.out.println("go out...")
    }
}
class Boy extends Person {
    public Boy(String name) {
        super(name);
    }

    public void beforeOut() {
        System.out.println("get up...")
    }
}

class Girl extends Person {
    public Boy(String name) {
        super(name);
    }

    public void beforeOut() {
        System.out.println("dress up...")
    }
}

golang 版

golang没有严格意义上的继承机制, 仅仅是利用组合的特性来模拟继承, 对于组合优于继承体现的还是很好的, 也带来了一些问题, 比如不能实现抽象方法, 上面的方法延迟实现等等

可以换一种思路来完成它, 用绑定!

type IPerson interface {
    SetName(name string)
    BeforeOut()
    Out()
}

接口和 java 大致相同,Person 结构体不同

type Person struct {
    Specific IPerson
    name     string
}
func (this *Person) SetName(name string) {
    this.name = name
}

func (this *Person) Out() {
    this.BeforeOut()
    fmt.Println(this.name + " go out...")
}

func (this *Person) BeforeOut() {
    if this.Specific == nil {
        return
    }

    this.Specific.BeforeOut()
}

第一个方法直接无视, 第二个和java 是一样的,

第三个方法,是java版Person没有去实现的, 按道理将这个BeforeOut是要延迟到子类去实现的

关键点就一句话, this.Specific.BeforeOut(), 直接调用了那个绑定的实例的BeforeOut方法,过用了一种折中的方式来实现方法的延迟实现

type Boy struct {
    Person
}

func (_ *Boy) BeforeOut() {
    fmt.Println("get up..")
}

type Girl struct {
    Person
}

func (_ *Girl) BeforeOut() {
    fmt.Println("dress up..")
}
func main() {
    var p *Person = &Person{}

    p.Specific = &Boy{}
    p.SetName("qibin")
    p.Out()

    p.Specific = &Girl{}
    p.SetName("loader")
    p.Out()
}

GOLANG接口适配,组合方式的灵活接口演化

http://blog.csdn.net/win_lin/article/details/72223168