Golang Design Patterns

2023-03-05T16:38:00

{collapse}
{collapse-item label="Decorator Pattern" open}
1.定义

装饰器模式是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

2.应用

这是一个计算Π的函数

func Pi(n int) float64 {
    ch := make(chan float64)

    for k := 0; k <= n; k++ {
        go func(ch chan float64, k float64) {
            ch <- 4 * math.Pow(-1, k) / (2*k + 1)
        }(ch, float64(k))
    }

    result := 0.0
    for k := 0; k <= n; k++ {
        result += <-ch
    }

    return result
}

现在如果需要在这个求Π的程序上增加一个功能,计算出求Π耗费的时间并将结果通过log对象打印出来,可以封装一个函数来增加这个功能,而不去破坏原来函数的结构

type piFunc func(int) float64

// logger(cache(Pi(n)))
func wraplogger(fun piFunc, logger *log.Logger) piFunc {
    return func(n int) float64 {
        fn := func(n int) (result float64) {
            defer func(t time.Time) {
                logger.Printf("took=%v, n=%v, result=%v", time.Since(t), n, result)
            }(time.Now())

            return fun(n)
        }
        return fn(n)
    }
}

写一个测试例子看下效果

func main() {
    g := wraplogger(pi, log.New(os.Stdout, "Test ", 1))
    g(100000)
    g(20000)
}

如果现在需要再增加一个缓存的功能,计算过的结果存起来,继续封装一个函数扩充pi的功能

// cache(logger(Pi(n)))
func wrapcache(fun piFunc, cache *sync.Map) piFunc {
    return func(n int) float64 {
        fn := func(n int) float64 {
            key := fmt.Sprintf("n=%d", n)
            val, ok := cache.Load(key)
            if ok {
                return val.(float64)
            }
            result := fun(n)
            cache.Store(key, result)
            return result
        }
        return fn(n)
    }
}

写个测试例子测试下

func main() {
    f := wrapcache(Pi, &sync.Map{})
    g := wraplogger(f, log.New(os.Stdout, "Test ", 1))

    g(100000)
    g(20000)
    g(100000)
}

如果现在不需要计算Π,而是对整数取半操作同时增加缓存,这个时候只需将wrapcache函数参数求Π函数换成取半函数即可

func divide(n int) float64 {
    return float64(n / 2)
}

func main() {
    f := wrapcache(divide, &sync.Map{})
    g := wraplogger(f, log.New(os.Stdout, "Divide ", 1))

    g(10000)
    g(2000)
    g(10)
    g(10000)
}

3.优缺点

优点缺点
无需创建新子类即可扩展对象的行为在封装器栈中删除特定封装器比较困难
可以在运行时添加或删除对象的功能实现行为不受装饰栈顺序影响的装饰比较困难
可以用多个装饰封装对象来组合几种行为各层的初始化配置代码看上去可能会很糟糕
单一职责原则,可以将实现了许多不同行为的一个大类拆分为多个较小的类

{/collapse-item}
{collapse-item label="Generator And Observer Pattern" open}
1.定义

生成器模式是一种创建型设计模式,使你能够分步骤创建复杂对象.该模式允许你使用相同的创建代码生成不同类型和形式的对象

观察者模式是一种行为设计模式,允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象

2.应用

生成器模式
生成0-n的斐波那契数列

package main

import "fmt"

func fib(n int) chan int {
    out := make(chan int)

    go func() {
        defer close(out)
        for i, j := 0, 1; i < n; i, j = i+j, i {
            out <- i
        }

    }()

    return out
}

// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

func main() {
    for x := range fib(10000000) {
        fmt.Println(x)
    }
}

观察者模式
观察者模式主要是两个角色,一个发布者,一个订阅者

先定义发布者的接口
发布者拥有增加/删除订阅者、通知订阅者的功能

type Subject interface {
    AddListener(Observer)
    RemoveListener(Observer)
    Notify(Event)
}

订阅者的接口
订阅者就是所说的观察者拥有订阅时间发生时被通知的接口

Observer interface {
    NotifyCallback(Event)
}

以下是通过实现发布者和订阅接口,实现的通知观察者斐波那契结果的功能

package main

package main

import (
    "fmt"
    "sync"
    "time"
)

type (
    Event struct {
        data int
    }

    Observer interface {
        NotifyCallback(Event)
    }

    Subject interface {
        AddListener(Observer)
        RemoveListener(Observer)
        Notify(Event)
    }

    eventObserver struct {
        id   int
        time time.Time
    }

    eventSubject struct {
        observers sync.Map
    }
)

func (e *eventObserver) NotifyCallback(event Event) {
    fmt.Printf("Observer: %d Recieved: %d after %v\n", e.id, event.data, time.Since(e.time))
}

func (s *eventSubject) AddListener(obs Observer) {
    s.observers.Store(obs, struct{}{})
}

func (s *eventSubject) RemoveListener(obs Observer) {
    s.observers.Delete(obs)
}

func (s *eventSubject) Notify(event Event) {
    s.observers.Range(func(key interface{}, value interface{}) bool {
        if key == nil || value == nil {
            return false
        }

        key.(Observer).NotifyCallback(event)
        return true
    })

}

func fib(n int) chan int {
    out := make(chan int)

    go func() {
        defer close(out)
        for i, j := 0, 1; i < n; i, j = i+j, i {
            out <- i
        }

    }()

    return out
}

// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

func main() {
    n := eventSubject{
        observers: sync.Map{},
    }

    t := time.Now()

    var obs1 = eventObserver{id: 1, time: t}
    var obs2 = eventObserver{id: 2, time: t}
    n.AddListener(&obs1)
    n.AddListener(&obs2)

    go func() {
        select {
        case <-time.After(time.Millisecond * 10):
            n.RemoveListener(&obs1)
        }
    }()

    for x := range fib(100000) {
        n.Notify(Event{data: x})
    }
}

3.优缺点

生成器模式

优点缺点
可以分步创建对象,暂缓创建步骤或递归运行创建步骤由于该模式需要新增多个类,因此代码整体复杂程度会有所增加
生成不同形式的产品时,可以复用相同的制造代码
单一职责原则,可以将复杂构造代码从产品的业务逻辑中分离出来

观察者模式

优点缺点
开闭原则,无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)订阅者的通知顺序是随机的
可以在运行时建立对象之间的联系

{/collapse-item}
{collapse-item label="Factory And Abstract Factory Pattern" open}
1.定义

工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型

抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

2.应用

工厂模式
当业务需要存储时,底层存储的方式有很多种实现,上层并不需要关心底层存储方式,故只需提供一个存储和获取数据的接口就行,就用到了工厂模式

type (
    mongoDB struct {
        database map[string]string
    }

    sqlite struct {
        database map[string]string
    }

    Database interface {
        GetData(string) string
        PutData(string, string)
    }
)

func (mdb mongoDB) GetData(query string) string {
    if _, ok := mdb.database[query]; !ok {
        return ""
    }

    fmt.Println("MongoDB")
    return mdb.database[query]
}

func (sql sqlite) GetData(query string) string {
    if _, ok := sql.database[query]; !ok {
        return ""
    }

    fmt.Println("Sqlite")
    return sql.database[query]
}

func (mdb mongoDB) PutData(query string, data string) {
    mdb.database[query] = data
}

func (sql sqlite) PutData(query string, data string) {
    sql.database[query] = data
}

func DatabaseFactory(env string) interface{} {
    switch env {
    case "production":
        return mongoDB{
            database: make(map[string]string),
        }
    case "development":
        return sqlite{
            database: make(map[string]string),
        }
    default:
        return nil
    }
}

抽象工厂模式
如果底层存储除了数据库存储,还需要扩展成文件存储的功能,文件存储只关心创建文件和获取文件,这个时候就需要再将工厂抽象一层,让他可以拥有不同类型的工厂的功能

type (
    mongoDB struct {
        database map[string]string
    }

    sqlite struct {
        database map[string]string
    }

    file struct {
        name    string
        content string
    }

    ntfs struct {
        files map[string]file
    }

    ext4 struct {
        files map[string]file
    }

    FileSystem interface {
        CreateFile(string)
        FindFile(string) file
    }

    Database interface {
        GetData(string) string
        PutData(string, string)
    }

    Factory func(string) interface{}
)

func (mdb mongoDB) GetData(query string) string {
    if _, ok := mdb.database[query]; !ok {
        return ""
    }

    fmt.Println("MongoDB")
    return mdb.database[query]
}

func (sql sqlite) GetData(query string) string {
    if _, ok := sql.database[query]; !ok {
        return ""
    }

    fmt.Println("Sqlite")
    return sql.database[query]
}

func (mdb mongoDB) PutData(query string, data string) {
    mdb.database[query] = data
}

func (sql sqlite) PutData(query string, data string) {
    sql.database[query] = data
}

func (ntfs ntfs) CreateFile(path string) {
    file := file{content: "NTFS file", name: path}
    ntfs.files[path] = file
    fmt.Println("NTFS")
}

func (ext ext4) CreateFile(path string) {
    file := file{content: "EXT4 file", name: path}
    ext.files[path] = file
    fmt.Println("EXT4")
}

func (ntfs ntfs) FindFile(path string) file {
    if _, ok := ntfs.files[path]; !ok {
        return file{}
    }

    return ntfs.files[path]
}

func (ext ext4) FindFile(path string) file {
    if _, ok := ext.files[path]; !ok {
        return file{}
    }

    return ext.files[path]
}

func FilesystemFactory(env string) interface{} {
    switch env {
    case "production":
        return ntfs{
            files: make(map[string]file),
        }
    case "development":
        return ext4{
            files: make(map[string]file),
        }
    default:
        return nil
    }
}

func DatabaseFactory(env string) interface{} {
    switch env {
    case "production":
        return mongoDB{
            database: make(map[string]string),
        }
    case "development":
        return sqlite{
            database: make(map[string]string),
        }
    default:
        return nil
    }
}

func AbstractFactory(fact string) Factory {
    switch fact {
    case "database":
        return DatabaseFactory
    case "filesystem":
        return FilesystemFactory
    default:
        return nil
    }
}

3.优缺点

工厂模式

优点缺点
可以避免创建者和具体产品之间的紧密耦合应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中
单一职责原则,你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护
开闭原则,无需更改现有客户端代码,你就可以在程序中引入新的产品类型

抽象工厂

优点缺点
可以确保同一工厂生成的产品相互匹配由于采用该模式需要向应用中引入众多接口和类,代码可能会比之前更加复杂
可以避免客户端和具体产品代码的耦合
单一职责原则,你可以将产品生成代码抽取到同一位置,使得代码易于维护
开闭原则,向应用程序中引入新产品变体时,你无需修改客户端代码

{/collapse-item}
{/collapse}

当前页面是本站的「Baidu MIP」版。发表评论请点击:完整版 »