杨 发布的文章

装饰模式(Decorator Pattern)是一种结构型设计模式,它允许动态地给一个对象添加新的功能,而无需修改其源代码。在 Golang 中实现装饰模式可以使用接口和组合来实现。

下面是一个使用 Golang 实现装饰模式的示例代码:

package main

import "fmt"

type Component interface {
    Operation()
}

type ConcreteComponent struct{}

func (c *ConcreteComponent) Operation() {
    fmt.Println("ConcreteComponent.Operation()")
}

type Decorator interface {
    Component
}

type ConcreteDecoratorA struct {
    component Component
}

func (d *ConcreteDecoratorA) Operation() {
    d.component.Operation()
    fmt.Println("ConcreteDecoratorA.Operation()")
}

type ConcreteDecoratorB struct {
    component Component
}

func (d *ConcreteDecoratorB) Operation() {
    d.component.Operation()
    fmt.Println("ConcreteDecoratorB.Operation()")
}

func main() {
    component := &ConcreteComponent{}

    decoratorA := &ConcreteDecoratorA{component}
    decoratorB := &ConcreteDecoratorB{decoratorA}

    decoratorB.Operation()
}

在上面的代码中,我们定义了一个 Component 接口,它包含一个 Operation 方法,用于执行操作。我们还定义了一个 ConcreteComponent 结构体,它实现了 Component 接口的 Operation 方法。

接着,我们定义了一个 Decorator 接口,它继承了 Component 接口,并定义了两个具体的装饰器类:ConcreteDecoratorA 和 ConcreteDecoratorB。这两个装饰器类都包含一个 component 成员变量,用于存储被装饰的组件对象。它们也都实现了 Operation 方法,该方法会先调用被装饰的组件对象的 Operation 方法,然后再执行自己的操作。

最后,在 main 函数中,我们创建了一个 ConcreteComponent 实例,并分别用 ConcreteDecoratorA 和 ConcreteDecoratorB 对其进行装饰。由于 ConcreteDecoratorA 和 ConcreteDecoratorB 都实现了 Component 接口,因此它们可以像 ConcreteComponent 一样被传递和使用。最终,我们调用 decoratorB.Operation() 方法,会先调用 ConcreteComponent.Operation() 方法,然后依次执行 ConcreteDecoratorA.Operation() 和 ConcreteDecoratorB.Operation() 方法。通过这种方式,我们可以动态地给一个对象添加新的功能,而无需修改其源代码。

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系,使得当一个对象的状态发生改变时,其所有依赖对象都会自动得到通知并更新。在 Golang 中实现观察者模式可以使用接口和函数来实现。

下面是一个使用 Golang 实现观察者模式的示例代码:

package main

import (
    "fmt"
    "sync"
)

type Observer interface {
    Update(msg string)
}

type Subject struct {
    observers []Observer
    mutex     sync.Mutex
}

func (s *Subject) RegisterObserver(observer Observer) {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    s.observers = append(s.observers, observer)
}

func (s *Subject) RemoveObserver(observer Observer) {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    for i, o := range s.observers {
        if o == observer {
            s.observers = append(s.observers[:i], s.observers[i+1:]...)
            break
        }
    }
}

func (s *Subject) NotifyObservers(msg string) {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    for _, observer := range s.observers {
        observer.Update(msg)
    }
}

type ConcreteObserver1 struct{}

func (o *ConcreteObserver1) Update(msg string) {
    fmt.Println("ConcreteObserver1 received message:", msg)
}

type ConcreteObserver2 struct{}

func (o *ConcreteObserver2) Update(msg string) {
    fmt.Println("ConcreteObserver2 received message:", msg)
}

func main() {
    subject := &Subject{}
    observer1 := &ConcreteObserver1{}
    observer2 := &ConcreteObserver2{}

    subject.RegisterObserver(observer1)
    subject.RegisterObserver(observer2)

    subject.NotifyObservers("Hello World!")

    subject.RemoveObserver(observer2)

    subject.NotifyObservers("Goodbye World!")
}

在上面的代码中,我们定义了一个 Observer 接口,它包含一个 Update 方法,用于接收通知。我们还定义了一个 Subject 结构体,它包含一个 observers 切片用于存储观察者对象,并使用 sync.Mutex 来保证线程安全。

在 Subject 结构体中,我们定义了三个方法:RegisterObserver、RemoveObserver 和 NotifyObservers。RegisterObserver 方法用于注册观察者对象,RemoveObserver 方法用于移除观察者对象,NotifyObservers 方法用于通知所有观察者对象。

接下来,我们定义了两个具体的观察者类:ConcreteObserver1 和 ConcreteObserver2,它们实现了 Observer 接口的 Update 方法,用于接收通知并输出消息。

最后,在 main 函数中,我们创建了一个 Subject 实例和两个观察者实例,并分别调用 RegisterObserver 方法来注册观察者对象。然后,我们调用 NotifyObservers 方法来通知所有观察者对象,并输出相应的消息。接着,我们调用 RemoveObserver 方法来移除一个观察者对象,再次调用 NotifyObservers 方法来

工厂模式(Factory Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但将具体的对象创建延迟到子类中。在 Golang 中实现工厂模式通常使用函数来实现。

下面是一个使用 Golang 实现工厂模式的示例代码:

package main

import "fmt"

type Product interface {
    GetName() string
}

type ProductA struct{}

func (p *ProductA) GetName() string {
    return "Product A"
}

type ProductB struct{}

func (p *ProductB) GetName() string {
    return "Product B"
}

func CreateProduct(name string) Product {
    switch name {
    case "A":
        return &ProductA{}
    case "B":
        return &ProductB{}
    default:
        return nil
    }
}

func main() {
    productA := CreateProduct("A")
    fmt.Println(productA.GetName())

    productB := CreateProduct("B")
    fmt.Println(productB.GetName())
}

在上面的代码中,我们定义了一个 Product 接口,它包含一个 GetName 方法,用于获取产品名称。接下来,我们定义了两个具体的产品类:ProductA 和 ProductB,它们实现了 Product 接口的 GetName 方法。

我们还定义了一个 CreateProduct 函数,该函数根据传入的产品名称来创建具体的产品实例。在 CreateProduct 函数中,我们使用 switch 语句根据产品名称来创建相应的产品实例。

最后,在 main 函数中,我们分别使用 CreateProduct 函数来创建 ProductA 和 ProductB 实例,并输出它们的名称。可以看到,我们只需要传入相应的产品名称,就可以创建相应的产品实例,而无需关心具体的产品实现细节。这正是工厂模式的优势所在,它将对象的创建与使用代码分离开来,使得客户端代码更加简洁和易于维护。

单例模式(Singleton Pattern)是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在 Golang 中实现单例模式可以使用 sync 包提供的 Once 和 Mutex 来实现。

下面是一个使用 Golang 实现单例模式的示例代码:

package main

import (
    "fmt"
    "sync"
)

type Singleton struct {
    data string
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{"Hello World!"}
    })
    return instance
}

func main() {
    s1 := GetInstance()
    s2 := GetInstance()

    if s1 == s2 {
        fmt.Println("Same instance")
    } else {
        fmt.Println("Different instance")
    }
}

在上面的代码中,我们定义了一个 Singleton 结构体,该结构体包含一个 data 字段。我们还定义了一个全局变量 instance,用于存储 Singleton 的唯一实例。

在 GetInstance 函数中,我们使用 sync.Once 来保证 instance 只被初始化一次。sync.Once 会在第一次调用 Do 方法时执行指定的函数,后续调用将被忽略。因此,只要我们通过 GetInstance 方法获取 instance,就可以保证 Singleton 的唯一性。

最后,在 main 函数中,我们分别使用 GetInstance 方法获取 Singleton 实例,并比较它们的地址是否相同。如果相同,则说明它们是同一个实例;否则,它们是不同的实例。

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并且使它们之间可以互相替换。该模式使得算法可以独立于使用它的客户端而变化。

下面是一个使用 Golang 实现策略模式的示例代码:

package main

import "fmt"

type PaymentStrategy interface {
    Pay(amount float64) error
}

type CreditCardStrategy struct{}

func (cc *CreditCardStrategy) Pay(amount float64) error {
    fmt.Printf("Paid %0.2f using Credit Card.\n", amount)
    return nil
}

type PayPalStrategy struct{}

func (pp *PayPalStrategy) Pay(amount float64) error {
    fmt.Printf("Paid %0.2f using PayPal.\n", amount)
    return nil
}

type PaymentContext struct {
    strategy PaymentStrategy
}

func (pc *PaymentContext) SetPaymentStrategy(strategy PaymentStrategy) {
    pc.strategy = strategy
}

func (pc *PaymentContext) MakePayment(amount float64) error {
    return pc.strategy.Pay(amount)
}

func main() {
    creditCardStrategy := &CreditCardStrategy{}
    payPalStrategy := &PayPalStrategy{}

    paymentContext := &PaymentContext{}

    // 使用信用卡支付
    paymentContext.SetPaymentStrategy(creditCardStrategy)
    paymentContext.MakePayment(100.00)

    // 使用 PayPal 支付
    paymentContext.SetPaymentStrategy(payPalStrategy)
    paymentContext.MakePayment(200.00)
}

在上面的代码中,我们定义了一个 PaymentStrategy 接口,该接口包含一个 Pay 方法,用于支付指定金额。接下来,我们定义了两个具体的支付策略类:CreditCardStrategy 和 PayPalStrategy,它们实现了 PaymentStrategy 接口的 Pay 方法。

我们还定义了一个 PaymentContext 结构体,该结构体包含一个 strategy 字段,用于存储当前的支付策略。PaymentContext 结构体还定义了两个方法:SetPaymentStrategy 用于设置支付策略,MakePayment 用于执行支付操作。

最后,在 main 函数中,我们创建了一个 CreditCardStrategy 和 PayPalStrategy 实例,并分别使用它们来进行支付。可以看到,在 MakePayment 方法中,我们只需要调用 strategy.Pay(amount) 来执行支付操作,而无需关心具体的支付策略是什么。这正是策略模式的优势所在,它将算法的实现细节与客户端代码隔离开来,使得客户端代码更加简洁和易于维护。

在 Go 语言中,协程是轻量级的用户态线程,也称为 goroutine,运行在操作系统的线程之上,是 Go 语言的核心特性之一。协程的调度是由 Go 语言运行时(runtime)实现的,下面是协程调度的基本流程:

当一个 goroutine 被创建时,它的执行是由主 goroutine 控制的。

如果该 goroutine 遇到了一个 I/O 操作、系统调用、时间延迟等需要阻塞的事件,则该 goroutine 会将自己从内部的队列中移除,直到该事件完成。

运行时系统会从运行队列中调度一个可运行的 goroutine 继续执行。

当一个 goroutine 执行完毕时,它也会自动从队列中退出。

如果主 goroutine 卡住了,运行时系统会尝试捕获 panic 以便程序能够正常退出。

当所有 goroutine 都结束并且 main 函数退出后,程序退出。

总的来说,Go 语言中的协程调度采用了 M:N 模型,即运行时系统会将多个 goroutine 映射到少量的操作系统线程上进行执行。这种方式有效地利用了多处理器系统的资源,并且可以轻松地处理大量的并发请求,提高了程序的执行效率和性能。

在普通的编程中,编写并发程序时需要特别注意并发安全性,以避免数据竞争和其他并发问题。在Go语言中,下面是一些可能会出现并发不安全问题的情况:

共享变量:如果多个 goroutine 对一个共享变量进行读写,而至少有一个 goroutine 执行写操作时,就有可能会出现并发问题。为了避免这种情况,可以使用 sync 包或通道来同步访问共享变量。

非原子操作:如果一个变量的更新操作不是原子的,就可能会导致并发问题。例如对于 32 位的整数类型,在一个 goroutine 更新该变量的高 16 位,而在另一个 goroutine 更新低 16 位时,就有可能出现错误结果。为了避免这种情况,可以使用原子操作进行变量更新。

无序写入:如果多个 goroutine 向同一个切片(slice)同时写入数据,而没有采取任何措施来同步访问该切片,就有可能出现并发问题。为了避免这种情况,可以使用 sync 包或通道来同步访问共享数据。

共享 map:如果多个 goroutine 对同一个 map 进行读写操作,而至少有一个 goroutine 执行写操作时,就可能会出现并发问题。为了避免这种情况,可以使用 sync 包或者采用只读方式访问 map。

总的来说,Go语言具有强大的并发性能,而并发不安全问题主要是由于多个 goroutine 访问共享数据而引起的。开发人员在编写并发代码的同时,应该采取一些正确地同步机制,保证每个共享变量的访问被合理的同步以避免并发不安全问题。

func (db *DB) Unscoped() (tx *DB) {
    tx = db.getInstance()
    tx.Statement.Unscoped = true
    return
}

Unscoped办法设置tx.Statement.Unscoped为true

gorm的Select、Delete方法都是软删除,Select会自动筛选delete_at为空的记录,Delete会更新delete_at。

如果需要获取已删除的记录,可以tx.Unscoped().Select()。
如果需要物理删除,可以tx.Unscoped().Delete().