golang 设计模式

发布时间 2024-01-08 11:40:10作者: haonan071

GO程序设计模式

创建型模式

单例模式

单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。

概念示例

通常而言,单例实例会在结构体首次初始化时创建。 为了实现这一操作, 我们在结构体中定义一个 getInstance获取实例方法。 该方法将负责创建和返回单例实例。 创建后, 每次调用 getInstance时都会返回相同的单例实例。

协程方面又有什么需要注意的吗? 每当多个协程想要访问实例时, 单例结构体就必须返回相同的实例。 正因如此, 单例设计模式的实施工作很容易出错。 下方的例子表示了创建单例的正确方式。

一些值得注意的地方:

  • 最开始时会有 nil检查, 确保 single.Instance单例实例在最开始时为空。 这是为了防止在每次调用 getInstance方法时都去执行消耗巨大的锁定操作。 如果检查不通过, 则就意味着 singleInstance字段已被填充。
  • singleInstance结构体将在锁定期间创建。
  • 在获取到锁后还会有另一个 nil检查。 这是为了确保即便是有多个协程绕过了第一次检查, 也只能有一个可以创建单例实例。 否则, 所有协程都会创建自己的单例结构体实例。

代码示例

package main

import (
    "fmt"
    "sync"
)

var lock = &sync.Mutex{}

type single struct {
}

var singleInstance *single

func getInstance() *single {
    if singleInstance == nil {
        lock.Lock()
        defer lock.Unlock()
        if singleInstance == nil {
            fmt.Println("Creating single instance now.")
            singleInstance = &single{}
        } else {
            fmt.Println("Single instance already created.")
        }
    } else {
        fmt.Println("Single instance already created.")
    }

    return singleInstance
}

func main() {

    for i := 0; i < 30; i++ {
        time.Sleep(1 * time.Second)
        go getInstance()
    }

    // Scanln is similar to Scan, but stops scanning at a newline and
    // after the final item there must be a newline or EOF.
    fmt.Scanln()
}

输出结果

Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.

另一个列子

  1. init函数
    我们可以在 init函数中创建单例实例。 这仅适用于实例的早期初始化工作已经确定时。 ​ init函数仅会在包中的每个文件里调用一次, 所以我们可以确定其只会创建一个实例。
  2. sync.Once仅会执行一次操作。 可查看下面的代码:
package main

import (
    "fmt"
    "sync"
)

var once sync.Once

type single struct {
}

var singleInstance *single

func getInstance() *single {
    if singleInstance == nil {
        once.Do(
            func() {
                fmt.Println("Creating single instance now.")
                singleInstance = &single{}
            })
    } else {
        fmt.Println("Single instance already created.")
    }

    return singleInstance
}

func main() {

    for i := 0; i < 30; i++ {
        time.Sleep(1 * time.Second)
        go getInstance()
    }

    // Scanln is similar to Scan, but stops scanning at a newline and
    // after the final item there must be a newline or EOF.
    fmt.Scanln()
}

结构型模式

适配器模式

原理: 适配器是一种结构型设计模式, 它能使不兼容的对象能够相互合作。适配器可担任两个对象间的封装器, 它会接收对于一个对象的调用, 并将其转换为另一个对象可识别的格式和接口。

概念示例

这里有一段客户端代码, 用于接收一个对象 (Lightning 接口) 的部分功能, 不过我们还有另一个名为 adaptee 的对象 (Windows 笔记本), 可通过不同的接口 (USB 接口) 实现相同的功能

这就是适配器模式发挥作用的场景。 我们可以创建这样一个名为 adapter 的结构体:

  • 遵循符合客户端期望的相同接口 (Lightning 接口)
  • 可以适合被适配对象的方式对来自客户端的请求进行 “翻译”。 适配器能够接受来自 Lightning 连接器的信息, 并将其转换成 USB 格式的信号, 同时将信号传递给 Windows 笔记本的 USB 接口。

代码示例

package main

import "fmt"

/*
这里有一段客户端代码, 用于接收一个对象 (Lightning 接口) 的部分功能, 不过我们还有另一个名为 adaptee 的对象 (Windows 笔记本),

	可通过不同的接口 (USB 接口) 实现相同的功能

这就是适配器模式发挥作用的场景。 我们可以创建这样一个名为 adapter 的结构体:
遵循符合客户端期望的相同接口 (Lightning 接口)。
可以适合被适配对象的方式对来自客户端的请求进行 “翻译”。 适配器能够接受来自 Lightning 连接器的信息,

	并将其转换成 USB 格式的信号, 同时将信号传递给 Windows 笔记本的 USB 接口。
*/
type Client struct{}

func (c *Client) InsertLightningConnectorIntoComputer(com Computer) {
	fmt.Println("Client inserts Lightning connector into computer.")
	com.InsertIntoLightningPort()
}

type Computer interface {
	InsertIntoLightningPort()
}

type Mac struct{}

func (m *Mac) InsertIntoLightningPort() {
	fmt.Println("Lightning connector is plugged into mac machine.")
}

type Windows struct{} // 未知其他服务

func (m *Windows) insertIntoUSBPort() {
	fmt.Println("USB connector is plugged into windows machine.")
}

// 适配器模式开始适配
type WindowsAdapter struct {
	windowMachine *Windows
}

func (w *WindowsAdapter) InsertIntoLightningPort() {
	fmt.Println("Adapter converts Lightning signal to USB.")
	w.windowMachine.insertIntoUSBPort()
}

type TypeC struct{} // 在适配个typeC

func (m *TypeC) insertIntoTypeCPort() {
	fmt.Println("typec connector is plugged into machine.")
}

type TypeCAdapter struct {
	TypeCMachine *TypeC
}

func (t *TypeCAdapter) InsertIntoLightningPort() {
	fmt.Println("Adapter converts Lightning signal to type-c")
	t.TypeCMachine.insertIntoTypeCPort()
}

func main() {
	client := &Client{} // 实例化client
	mac := &Mac{}       // 初始化mac
	client.InsertLightningConnectorIntoComputer(mac)
	// 适配器调用
	windowsMachine := &Windows{}
	windowsMachineAdapter := &WindowsAdapter{
		windowMachine: windowsMachine,
	}
	typec := &TypeC{}
	typecAdap := &TypeCAdapter{
		TypeCMachine: typec,
	}
	client.InsertLightningConnectorIntoComputer(windowsMachineAdapter)
	client.InsertLightningConnectorIntoComputer(typecAdap)
}

输出结果

// 输出结果
Client inserts Lightning connector into computer.
Lightning connector is plugged into mac machine.
Client inserts Lightning connector into computer.
Adapter converts Lightning signal to USB.
USB connector is plugged into windows machine.
Client inserts Lightning connector into computer.
Adapter converts Lightning signal to type-c
typec connector is plugged into machine.

桥接模式

桥接是一种结构型设计模式可将业务逻辑或一个大类拆分为不同的层次结构, 从而能独立地进行开发。
层次结构中的第一层 (通常称为抽象部分) 将包含对第二层 (实现部分) 对象的引用。 抽象部分将能将一些 (有时是绝大部分) 对自己的调用委派给实现部分的对象。 所有的实现部分都有一个通用接口, 因此它们能在抽象部分内部相互替换。

概念示例

假设你有两台电脑: 一台 Mac 和一台 Windows。 还有两台打印机: 爱普生和惠普。 这两台电脑和打印机可能会任意组合使用。 客户端不应去担心如何将打印机连接至计算机的细节问题。如果引入新的打印机, 我们也不会希望代码量成倍增长。 所以, 我们创建了两个层次结构, 而不是 2x2 组合的四个结构体:

  • 抽象层: 代表计算机
  • 实施层: 代表打印机

这两个层次可通过桥接进行沟通, 其中抽象层 (计算机) 包含对于实施层 (打印机) 的引用。 抽象层和实施层均可独立开发, 不会相互影响。

代码示例

// 抽象的计算机 支持打印和设置使用那个打印机
type Computer interface {
    Print()
    SetPrinter(Printer)
}
// 精确抽象比如mac windows
type Mac struct {
    printer Printer
}

func (m *Mac) Print() {
    fmt.Println("Print request for mac")
    m.printer.PrintFile()
}

func (m *Mac) SetPrinter(p Printer) {
    m.printer = p
}
// 精确抽象后的windows
type Windows struct {
    printer Printer
}

func (w *Windows) Print() {
    fmt.Println("Print request for windows")
    w.printer.PrintFile()
}

func (w *Windows) SetPrinter(p Printer) {
    w.printer = p
}

// 实施接口约束
type Printer interface {
	PrintFile()
}

// 真正实施打印爱普生
type Epson struct {}

func (p *Epson) PrintFile() {
    fmt.Println("Printing by a epson printer")
}

// 真正实施打印惠普
type Hp struct {}

func (h *Hp) PrintFile() {
    fmt.Println("Printing by a HP printer")
}

// 客户端调用
func main() {

    hpPrinter := &Hp{}
    epsonPrinter := &Epson{}

    macComputer := &Mac{}

    macComputer.SetPrinter(hpPrinter)
    macComputer.Print()
    fmt.Println()

    macComputer.SetPrinter(epsonPrinter)
    macComputer.Print()
    fmt.Println()

    winComputer := &Windows{}

    winComputer.SetPrinter(hpPrinter)
    winComputer.Print()
    fmt.Println()

    winComputer.SetPrinter(epsonPrinter)
    winComputer.Print()
    fmt.Println()
}

执行结果

Print request for mac
Printing by a HP printer

Print request for mac
Printing by a epson printer

Print request for windows
Printing by a HP printer

Print request for windows
Printing by a epson printer

组合模式

组合是一种结构型设计模式,你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。
对于绝大多数需要生成树状结构的问题来说, 组合都是非常受欢迎的解决方案。 组合最主要的功能是在整个树状结构上递归调用方法并对结果进行汇总。

装饰模式

外观模式

享元模式

代理模式