设计模式(Golang)

发布于:2024-04-16 ⋅ 阅读:(29) ⋅ 点赞:(0)

一、设计原则

软件设计原则是一组指导软件开发人员进行系统设计、模块划分、类和接口定义、代码组织等方面的准则,旨在提高软件的可维护性、可扩展性、灵活性和重用性。以下是软件设计领域中广泛认可的一些核心原则:

1. 开闭原则 (Open-Closed Principle, OCP)

开放(Open):对扩展开放,即允许在不修改现有代码的基础上扩展系统的行为或功能。这意味着应通过扩展而非修改已有类或模块来应对需求变化。

封闭(Closed):对修改封闭,即已有的代码(尤其是稳定的部分)应尽量避免改动。这通常通过抽象和依赖注入来实现,使系统能够在不改动现有代码的情况下添加新功能或适应新情况。

2. 单一职责原则 (Single Responsibility Principle, SRP)

一个类、模块或函数应有且仅有一个引起它变化的原因。换句话说,每个类或模块应专注于一个特定的职责或功能领域,避免承担过多责任。这样有助于保持代码的高内聚性和低耦合性,使得代码更易于理解和测试。

3. 里氏替换原则 (Liskov Substitution Principle, LSP)

子类对象应当能够替换其基类对象出现在任何位置,且不会导致程序的正确性受到影响。这意味着子类必须遵守基类的约定,且不应破坏基类的行为。LSP 强调了继承层次中行为的兼容性,是实现多态和代码复用的重要基础。

4. 依赖倒置原则 (Dependency Inversion Principle, DIP)

高层模块不应依赖于低层模块,两者都应依赖于抽象(接口或抽象类)。具体而言:

  • 高层模块(政策制定者)定义抽象。
  • 低层模块(策略实现者)实现抽象。
  • 抽象不应依赖于细节,细节(具体实现)应依赖于抽象。

DIP 促进了模块间的解耦,使得代码更容易适应变化,同时鼓励面向接口编程。

5. 接口隔离原则 (Interface Segregation Principle, ISP)

客户端不应该被迫依赖它不需要的接口方法。一个大而全的接口应该拆分为多个更小、更专注的接口,每个接口只包含客户端实际需要的方法。ISP 避免了胖接口带来的冗余和耦合,使接口更易于理解和使用。

6. 迪米特法则 (Law of Demeter, LoD) 或最少知识原则 (Least Knowledge Principle)

一个对象应当对其他对象有最少的了解。也就是说,一个对象应尽量减少与其他对象的直接交互,只与“朋友”(直接关联的对象)交谈。迪米特法则有助于减少系统的耦合度,提高模块间的独立性。

7. 合成复用原则 (Composite Reuse Principle, CRP)

优先使用对象组合(has-a 或 contains-a 关系)而不是类继承(is-a 关系)来实现复用。继承在某些场景下是有用的,但它可能导致紧耦合和脆弱的基类。合成则提供了更灵活的结构,允许在运行时动态地改变对象间的协作关系。

应用原则的注意事项

  • 原则之间存在关联:在实际应用中,这些原则往往相互交织,共同指导设计决策。
  • 权衡与折衷:没有绝对的最佳实践,不同原则在特定场景下可能需要权衡取舍。
  • 具体情况具体分析:原则是指导而非教条,应根据项目的具体需求、技术栈、团队习惯等因素灵活运用。

遵循这些软件设计原则,有助于构建出更加健壮、易于维护和扩展的软件系统。设计时应综合考虑这些原则,并结合具体的项目背景和团队共识,做出最适合当前项目的决策。

二、设计模式

创建型模式

工厂模式

目的:提供一个创建对象的统一接口,隐藏对象的具体创建过程。

Go 实现:使用函数或方法返回特定接口类型的实例,根据输入参数或其他条件选择创建不同类型的对象。

type Product interface {
    Operation()
}

type ConcreteProductA struct{}
func (p *ConcreteProductA) Operation() {}

type ConcreteProductB struct{}
func (p *ConcreteProductB) Operation() {}

func Factory(productType string) Product {
    switch productType {
    case "A":
        return &ConcreteProductA{}
    case "B":
        return &ConcreteProductB{}
    default:
        panic("Unknown product type")
    }
}

// 使用
product := Factory("A")
product.Operation()
单例模式

目的:确保一个类只有一个实例,并提供一个全局访问点。

Go 实现:利用 sync.Once 和全局变量确保单例对象只被初始化一次。

import "sync"

type Singleton struct{}

var (
    instance *Singleton
    once     sync.Once
)

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}
建造者模式

目的:将复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示。

Go 实现:定义一个 Builder 接口,实现多个具体的 Builder 类型,客户端通过调用 Builder 的方法逐步构造对象,最后调用 Build 方法得到完整对象。

type Vehicle interface {
    Describe() string
}

type CarBuilder struct {
    wheels   int
    doors    int
    color    string
    engine   string
}

func (cb *CarBuilder) SetWheels(wheels int)   { cb.wheels = wheels }
func (cb *CarBuilder) SetDoors(doors int)     { cb.doors = doors }
func (cb *CarBuilder) SetColor(color string)  { cb.color = color }
func (cb *CarBuilder) SetEngine(engine string) { cb.engine = engine }

func (cb *CarBuilder) Build() Vehicle {
    return &Car{
        wheels:   cb.wheels,
        doors:    cb.doors,
        color:    cb.color,
        engine:   cb.engine,
    }
}

type Car struct {
    wheels   int
    doors    int
    color    string
    engine   string
}

func (c *Car) Describe() string {
    return fmt.Sprintf("Car with %d wheels, %d doors, color %s, engine %s",
        c.wheels, c.doors, c.color, c.engine)
}

// 使用
builder := &CarBuilder{}
builder.SetWheels(4).SetColor("Red").SetEngine("V6")
vehicle := builder.Build()
vehicle.Describe()

结构型模式

适配器模式

目的:将一个类的接口转换成客户希望的另一个接口,使得原本不兼容的类可以一起工作。

Go 实现:创建一个新的类型,该类型包含了需要适配的类型,并实现目标接口。

type Target interface {
    Request()
}

type Adaptee struct{}

func (a *Adaptee) SpecificRequest() {}

type Adapter struct {
    adaptee *Adaptee
}

func (a *Adapter) Request() {
    a.adaptee.SpecificRequest()
}

// 客户端代码使用 Target 接口,无需知道 Adapter 内部使用了 Adaptee
装饰者模式

目的:动态地给对象添加额外的责任或行为。

Go 实现:通过组合(包含)原对象,创建装饰者对象,并在装饰者中扩展或修改原对象的行为。

type Component interface {
    Operation() string
}

type ConcreteComponent struct{}

func (cc *ConcreteComponent) Operation() string {
    return "ConcreteComponent.Operation()"
}

type Decorator struct {
    component Component
}

func (d *Decorator) Operation() string {
    return d.component.Operation()
}

type ConcreteDecoratorA struct {
    Decorator
}

func (cd *ConcreteDecoratorA) Operation() string {
    originalOp := cd.Decorator.Operation()
    return fmt.Sprintf("ConcreteDecoratorA.Operation() -> %s", originalOp)
}

// 使用
component := &ConcreteComponent{}
decorated := &ConcreteDecoratorA{Decorator: Decorator{component: component}}
fmt.Println(decorated.Operation())

行为型模式

观察者模式

目的:定义对象间一对多的依赖关系,当一个对象状态改变时,所有依赖于它的对象都会收到通知并自动更新。

Go 实现:使用 channels 和 goroutines 实现异步通知,或利用 sync.Cond 实现同步通知。

type Subject interface {
    Register(Observer)
    Unregister(Observer)
    NotifyObservers()
}

type ConcreteSubject struct {
    observers []Observer
    state     string
}

func (s *ConcreteSubject) Register(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *ConcreteSubject) Unregister(o Observer) {
    // ...
}

func (s *ConcreteSubject) NotifyObservers() {
    for _, observer := range s.observers {
        go observer.Update(s.state)
    }
}

type Observer interface {
    Update(state string)
}

// 实现 Observer 接口的具体观察者
策略模式

目的:定义一系列算法,并将每个算法封装为一个单独的类,使得算法可以在运行时进行切换。

Go 实现:定义一个接口(策略),实现多个具体的策略类型,客户端根据需要选择并传递合适的策略对象。

type Strategy interface {
    Calculate(numbers []int) int
}

type AddStrategy struct{}

func (as *AddStrategy) Calculate(numbers []int) int {
    sum := 0
    for _, n := range numbers {
        sum += n
    }
    return sum
}

type MultiplyStrategy struct{}

func (ms *MultiplyStrategy) Calculate(numbers []int) int {
    product := 1
    for _, n := range numbers {
        product *= n
    }
    return product
}

func Process(numbers []int, strategy Strategy) int {
    return strategy.Calculate(numbers)
}

// 使用
result := Process([]int{1, 2, 3}, &AddStrategy{})
fmt.Println(result)  // 输出 6

result = Process([]int{1, 2, 3}, &MultiplyStrategy{})
fmt.Println(result)  // 输出 6

其他模式

除了上述示例,Go 语言中还可以实现诸如模板方法模式、命令模式、迭代器模式、中介者模式、备忘录模式、解释器模式、状态模式、访问者模式、责任链模式等。在应用设计模式时,应遵循设计原则,结合 Go 语言的特性(如接口、并发模型等),并根据具体需求进行调整和创新。