Golang 40_Golang设计模式汇集

一、设计模式简介

1.1 设计模式简介

设计模式(Design Patterns) 是面向对象编程中的常用概念,它可以提高代码的复用性、可读性和可维护性,是软件开发中用于解决常见设计问题的通用解决方案。它们为软件架构师和开发人员提供了一套标准化的思路和实践,以便在开发过程中应对特定的设计挑战。

Golang 是一种静态类型、编译型语言,因其简洁、并发性强和性能优越而广受欢迎。在Golang中,常用的设计模式可以分为三大类:创建型设计模式结构型设计模式行为型设计模式

二、创建型设计模式

2.1 单例模式(Singleton Pattern)

单例模式(Singleton Pattern) 是一种创建型设计模式,它提供了一种创建实例对象的方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

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

单例模式的实现主要有2种方式:

  • 懒汉模式:使用到时才实例化(GetInstance),通过 once.Do 确保只有在第一次调用 GetInstance() 时才进行实例化。

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// file: singleton.go

package singleton

import (
	"sync"
)

// 懒汉式:使用到(调用 GetInstance)时才实例化
var (
	instance *singleton
	once     = sync.Once{}
)

type singleton struct {
    name string
}

func (s *singleton) GetInstanceName() string {
    return s.name
}

func GetInstance(name string) *singleton {
    // 使用了 sync.Once 实现了线程安全,确保只有在第一次调用 GetInstance() 时才进行实例化。
	once.Do(func() {
		instance = &singleton{name: name}
	})
	return instance
}

// file: singleton_test.go

package singleton

import (
    "testing"
)

func TestSingleton(t *testing.T) {
    instance1 := GetInstance("Singleton1")
    t.Logf("instance1 = %p, name=%s\n", instance1, instance1.GetInstanceName())

    instance2 := GetInstance("Singleton2")
    t.Logf("instance2 = %p, name=%s\n", instance2, instance2.GetInstanceName())

    if (instance1 != instance2 && instance1.GetInstanceName() != instance2.GetInstanceName()) {
        t.Errorf("Singleton test failed...")
    }
}

// run cmd: go test -v 
=== RUN   Test
    singleton_test.go:9: instance1 = 0xc000048640, name=Singleton1
    singleton_test.go:12: instance2 = 0xc000048640, name=Singleton1
--- PASS: Test (0.00s)
PASS
ok  	SourceCodeTest/Singleton	1.673s
  • 饿汉模式:一开始(进程启动初始化时)就实例化,实现方式为直接 给全局变量赋值初始化 或 放在 init 方法里进行初始化,程序一启动就创建好全局实例。

饿汉模式将在包加载的时候就会创建单例对象,当程序中用不到该对象时,浪费了一部分空间,但是相对于懒汉模式,不需要进行了加锁操作,会更安全,但是会减慢启动速度。

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// file: singleton.go

package singleton

type singleton struct {
    name string
}

func (s *singleton) GetInstanceName() string {
    return s.name
}

// 方式一:全局变量直接赋值
var instance = new(singleton{name: "Singleton1"})

func GetInstance()  *singleton{
    return instance
}

// 方式二:在 init 函数中初始化赋值
var instance2 *singleton

func init()  {
    instance2 = new(singleton{name: "Singleton2"})
}

func GetInstance2()  *singleton{
    return instance2
}

Tips: 根据 Golang 的执行顺序,全局变量的初始化会先于包的 init 函数先执行,其它没有特别的差距。

2.2 工厂模式(Factory Pattern)

工厂模式(Factory Pattern) 是一种创建型设计模式,它通过定义一个创建对象的接口,使子类决定实例化哪一个类。工厂模式可以使一个类的实例化延迟到其子类。在 Go 语言中,工厂模式通常通过接口和结构体组合来实现。

旨在提供一种封装对象创建过程的方式,使得客户端代码不必直接依赖于具体的对象创建细节。

工厂模式(Factory Pattern) 中,通常会定义一个 工厂接口,该接口包含一个用于创建产品的方法。然后针对每种产品,都实现一个具体的工厂类,用于创建该产品的实例。调用代码时只需要通过工厂接口来创建对象,而无需关心具体的创建过程。

工厂模式有三个主要变种:简单工厂模式(Simple Factory Pattern)工厂方法模式(Factory Method Pattern)抽象工厂模式(Abstract Factory Pattern)

2.2.1 简单工厂模式(Simple Factory Pattern)

简单工厂模式(Simple Factory Pattern) 通过一个工厂类来负责创建所有产品的实例对象。工厂类包含一个方法,该方法根据传入的参数决定创建哪种具体的对象。使用者通过向工厂传递参数来指定要创建的产品类型对象。这种模式简单易懂,但缺点是不够灵活,当需要添加新的产品类型时,需要修改工厂类。

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// file: product.go

package product

// Product 是一个接口,定义了所有产品都需要实现的方法
type Product interface {
    Use() string
}

// ProductA 是一种具体产品,实现了 Product 接口
type ProductA struct{}

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

// ProductB 是另一种具体产品,实现了 Product 接口
type ProductB struct{}

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

// SimpleFactory 是简单工厂类
type SimpleFactory struct{}

// CreateProduct 根据传入的类型创建具体产品
func (f *SimpleFactory) CreateProduct(t string) Product {
    switch t {
    case "A":
        return &ProductA{}
    case "B":
        return &ProductB{}
    default:
        return nil
    }
}

// file: product_test.go

package product

import (
    "testing"
)

func TestSimpleFactory(t *testing.T) {
    factory := &SimpleFactory{}

    productA := factory.CreateProduct("A")
    t.Log(productA.Use())

    productB := factory.CreateProduct("B")
    t.Log(productB.Use())
}

2.2.2 工厂方法模式(Factory Method Pattern)

工厂方法模式(Factory Method Pattern) 将具体产品实例的创建延迟到具体的工厂类中。每个产品都有对应的工厂类,使用者通过调用具体工厂类的方法来创建产品。这种模式可以实现产品与工厂的解耦,支持扩展,但可能会导致类的数量增加(每个产品类 需要对应一个 工厂类)。

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// file: product.go

package product

// Product 是一个接口,定义了所有产品都需要实现的方法
type Product interface {
    Use() string
}

// ProductA 是一种具体产品,实现了 Product 接口
type ProductA struct{}

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

// ProductB 是另一种具体产品,实现了 Product 接口
type ProductB struct{}

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

// Factory 是工厂接口,定义了创建产品的方法
type Factory interface {
    CreateProduct() Product
}

// FactoryA 是具体工厂,实现了 Factory 接口
type FactoryA struct{}

func (f *FactoryA) CreateProduct() Product {
    return &ProductA{}
}

// FactoryB 是另一种具体工厂,实现了 Factory 接口
type FactoryB struct{}

func (f *FactoryB) CreateProduct() Product {
    return &ProductB{}
}

// file: product_test.go

package product

import (
    "testing"
)

func TestSimpleFactory(t *testing.T) {
    var factory Factory

    factory = &FactoryA{}
    product := factory.CreateProduct()
    fmt.Println(product.Use())

    factory = &FactoryB{}
    product = factory.CreateProduct()
    fmt.Println(product.Use())
}

2.2.3 抽象工厂模式(Abstract Factory Pattern)

抽象工厂模式(Abstract Factory Pattern) 提供一个创建一系列相关或依赖对象的接口,而无需指定它们具体的类。每个工厂类都可以创建一组相关的产品,从而使得产品之间的兼容性更好。这种模式适用于创建复杂的对象族,但增加新的产品类型可能需要修改接口及所有的实现。在Golang中,抽象工厂模式可以通过接口和结构体的组合来实现。

具体的示例:假设我们要创建一个操作系统的UI组件工厂,其中包含按钮和文本框。根据操作系统的不同,我们可能会有不同的实现,比如Windows和MacOS的UI组件,步骤如下:

  1. 定义抽象产品接口:
  • 按钮接口(Button)
  • 文本框接口(TextBox)
  1. 定义具体产品实现:
  • Windows按钮(WindowsButton)
  • Windows文本框(WindowsTextBox)
  • MacOS按钮(MacButton)
  • MacOS文本框(MacTextBox)
  1. 定义抽象工厂接口:
  • UI工厂接口(UIFactory)
  1. 定义具体工厂实现:
  • Windows工厂(WindowsFactory)
  • MacOS工厂(MacFactory)

代码示例:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// file: ui_component.go

package ui_component

import (
    "fmt"
)

// Button 是按钮接口
type Button interface {
    Paint()
}

// TextBox 是文本框接口
type TextBox interface {
    Render()
}

// WindowsButton 是 Windows 系统的具体按钮实现
type WindowsButton struct{}

func (b *WindowsButton) Paint() {
    fmt.Println("Rendering a button in Windows style.")
}

// MacButton 是 MacOS 系统的具体按钮实现
type MacButton struct{}

func (b *MacButton) Paint() {
    fmt.Println("Rendering a button in MacOS style.")
}

// WindowsTextBox 是 Windows 系统的具体文本框实现
type WindowsTextBox struct{}

func (tb *WindowsTextBox) Render() {
    fmt.Println("Rendering a textbox in Windows style.")
}

// MacTextBox 是 MacOS 系统的具体文本框实现
type MacTextBox struct{}

func (tb *MacTextBox) Render() {
    fmt.Println("Rendering a textbox in MacOS style.")
}

// UIFactory 是抽象工厂接口,定义了创建一系列相关对象的方法
type UIFactory interface {
    CreateButton() Button
    CreateTextBox() TextBox
}

// WindowsFactory 是 Windows 系统的具体工厂实现
type WindowsFactory struct{}

func (f *WindowsFactory) CreateButton() Button {
    return &WindowsButton{}
}

func (f *WindowsFactory) CreateTextBox() TextBox {
    return &WindowsTextBox{}
}

// MacFactory 是 MacOS 系统的具体工厂实现
type MacFactory struct{}

func (f *MacFactory) CreateButton() Button {
    return &MacButton{}
}

func (f *MacFactory) CreateTextBox() TextBox {
    return &MacTextBox{}
}

// file: ui_component_test.go

package ui_component

import (
    "testing"
)

func TestUIComponent(t *testing.T) {
    // 使用抽象工厂创建产品

    var factory UIFactory

    // 创建 Windows 风格的 UI 组件
    factory = &WindowsFactory{}
    button := factory.CreateButton()
    textBox := factory.CreateTextBox()
    button.Paint()
    textBox.Render()

    // 创建 MacOS 风格的 UI 组件
    factory = &MacFactory{}
    button = factory.CreateButton()
    textBox = factory.CreateTextBox()
    button.Paint()
    textBox.Render()
}

解释

  • 抽象产品接口:定义了不同产品的公共方法,如Button接口和TextBox接口。
  • 具体产品实现:具体产品实现了抽象产品接口,如WindowsButton、MacButton、WindowsTextBox和MacTextBox。
  • 抽象工厂接口:定义了创建不同产品的方法,如UIFactory接口。
  • 具体工厂实现:具体工厂实现了抽象工厂接口,创建具体产品,如WindowsFactory和MacFactory。

通过这种设计,我们可以很容易地增加新的产品系列(比如Linux 或 Android 风格的UI组件),只需要实现新的具体产品和具体工厂,而不需要修改现有代码。这就是抽象工厂模式的强大之处,提供了良好的扩展性和维护性

2.3 创建者模式(Builder Pattern)

创建者模式(Builder Pattern) 是一种创建型设计模式,它将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示(将一个复杂对象的构建分离成多个简单对象的构建组合)。创建者模式特别适用于以下情况:

  • 需要创建的对象由多个部分组成。
  • 对象的创建过程需要灵活、可扩展。
  • 构建过程需要独立于对象的具体类型。

在Golang中,创建者模式可以通过定义一个创建者接口和具体的创建者结构体来实现。

具体的示例:假设我们要创建一个复杂的产品,例如一辆汽车,它包含多个部分(引擎、轮子、车身等)。我们使用创建者模式来构建不同类型的汽车(比如SUV和跑车),步骤如下:

  1. 定义产品:
  • 汽车结构体(Car)
  1. 定义创建者接口:
  • 汽车创建者接口(CarBuilder)
  1. 定义具体创建者实现:
  • SUV创建者(SUVBuilder)
  • 跑车创建者(SportsCarBuilder)
  1. 定义指导者(Director):
  • 指导者结构体(Director)

示例代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
// file: car.go

package car

// Car 是一个包含多个部分的复杂产品
type Car struct {
    Engine string
    Wheels string
    Body   string
}

// CarBuilder 是创建者接口,定义了构建产品的方法
type CarBuilder interface {
    SetEngine() CarBuilder
    SetWheels() CarBuilder
    SetBody() CarBuilder
    Build() *Car
}

// SUVBuilder 是具体创建者,实现了 CarBuilder 接口
type SUVBuilder struct {
    car *Car
}

func NewSUVBuilder() *SUVBuilder {
    return &SUVBuilder{car: &Car{}}
}

func (b *SUVBuilder) SetEngine() CarBuilder {
    b.car.Engine = "SUV Engine"
    return b
}

func (b *SUVBuilder) SetWheels() CarBuilder {
    b.car.Wheels = "SUV Wheels"
    return b
}

func (b *SUVBuilder) SetBody() CarBuilder {
    b.car.Body = "SUV Body"
    return b
}

func (b *SUVBuilder) Build() *Car {
    return b.car
}

// SportsCarBuilder 是另一种具体创建者,实现了 CarBuilder 接口
type SportsCarBuilder struct {
    car *Car
}

func NewSportsCarBuilder() *SportsCarBuilder {
    return &SportsCarBuilder{car: &Car{}}
}

func (b *SportsCarBuilder) SetEngine() CarBuilder {
    b.car.Engine = "Sports Car Engine"
    return b
}

func (b *SportsCarBuilder) SetWheels() CarBuilder {
    b.car.Wheels = "Sports Car Wheels"
    return b
}

func (b *SportsCarBuilder) SetBody() CarBuilder {
    b.car.Body = "Sports Car Body"
    return b
}

func (b *SportsCarBuilder) Build() *Car {
    return b.car
}

// Director 是指导者,负责管理构建过程
type Director struct {
    builder CarBuilder
}

func NewDirector(b CarBuilder) *Director {
    return &Director{builder: b}
}

func (d *Director) Construct() *Car {
    return d.builder.SetEngine().SetWheels().SetBody().Build()
}

// file: car_test.go

package car

import (
    "testing"
)

func TestCarBuilder(t *testing.T) {
    // 使用创建者模式构建产品
    suvBuilder := NewSUVBuilder()
    director := NewDirector(suvBuilder)
    suv := director.Construct()
    t.Logf("SUV: %+v\n", suv)

    sportsCarBuilder := NewSportsCarBuilder()
    director = NewDirector(sportsCarBuilder)
    sportsCar := director.Construct()
    t.Logf("Sports Car: %+v\n", sportsCar)
}

解释:

  • 产品:Car 结构体代表要构建的复杂对象。
  • 创建者接口:CarBuilder 接口定义了构建产品的各个步骤(SetEngine、SetWheels、SetBody 和 Build)。
  • 具体创建者:SUVBuilder 和 SportsCarBuilder 实现了 CarBuilder 接口,分别用于构建不同类型的汽车。
  • 指导者:Director 结构体负责管理构建过程,它接受一个 CarBuilder 接口,并通过调用构建步骤方法来构建产品。

优点:

  • 分离复杂的构建过程:创建者模式将复杂对象的构建过程分离出来,使代码更清晰、更容易维护。
  • 灵活性和可扩展性:可以方便地扩展新的具体创建者来构建不同的产品。
  • 一致的构建过程:指导者可以保证构建过程的一致性。

通过 创建者模式(Builder Pattern) 可以灵活地构建复杂的对象,代码结构清晰且易于扩展,适用于需要构建多种表示的复杂对象的场景。

2.4 原型模式(Prototype Pattern)

原型模式(Prototype Pattern) 是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过实例化类来创建对象。原型模式适用于需要创建大量相似对象的场景,可以提高创建对象的效率。Golang 中的原型模式可以通过实现 Clone 方法来实现对象的复制。

以下是一个使用 Golang 实现原型模式的示例:假设我们有一个图形接口(Shape),并且有两种具体图形实现:圆形(Circle)和矩形(Rectangle)。我们将使用原型模式来创建这些图形的副本。

  1. 定义图形接口(Shape)和具体图形实现(Circle 和 Rectangle)。
  2. 为每个具体图形实现 Clone 方法,以便能够复制自身。
  3. 使用原型模式创建图形副本。

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// file: shape.go

package shape

// Shape 是图形接口,定义了 Clone 方法
type Shape interface {
    Clone() Shape
    GetType() string
}

// Circle 是具体图形实现,表示圆形
type Circle struct {
    Radius int
}

func (c *Circle) Clone() Shape {
    return &Circle{Radius: c.Radius}
}

func (c *Circle) GetType() string {
    return "Circle"
}

// Rectangle 是具体图形实现,表示矩形
type Rectangle struct {
    Width  int
    Height int
}

func (r *Rectangle) Clone() Shape {
    return &Rectangle{Width: r.Width, Height: r.Height}
}

func (r *Rectangle) GetType() string {
    return "Rectangle"
}

// file: shape_test.go

package shape


import (
    "testing"
)

func TestCarBuilder(t *testing.T) {
    // 使用原型模式创建图形副本
    circle := &Circle{Radius: 10}
    rectangle := &Rectangle{Width: 20, Height: 10}

    shapes := []Shape{circle, rectangle}

    clones := []Shape{}
    for _, shape := range shapes {
        clone := shape.Clone()
        clones = append(clones, clone)
    }

    for i, clone := range clones {
        t.Logf("Clone %d: Type = %s\n", i+1, clone.GetType())
    }
}

解释:

  • 图形接口:Shape 接口定义了 Clone 方法和 GetType 方法,用于复制对象和获取对象类型。
  • 具体图形实现:Circle 和 Rectangle 分别实现了 Shape 接口,并实现了 Clone 方法以便复制自身。
  • 原型模式:在 main 函数中,我们创建了一些具体图形对象,并使用 Clone 方法创建它们的副本。

优点:

  • 提高对象创建效率:通过复制现有对象来创建新对象,而不是通过实例化类来创建对象,可以提高对象创建的效率。
  • 减少子类数量:通过原型模式可以减少需要创建的子类数量,因为可以通过复制原型对象来创建不同的对象。
  • 动态扩展:可以动态地改变对象的结构和属性,而不需要依赖于类的静态定义。

适用场景:

  • 需要创建大量相似对象的场景。
  • 创建对象的过程比较复杂或资源消耗较大的场景。
  • 想要避免使用构造函数或工厂方法来创建对象的场景。

通过原型模式,我们可以高效地创建对象的副本,并且代码结构清晰,易于维护和扩展。

三、结构型设计模式(Structural Design Patterns)

3.1 结构型设计模式简介

结构型设计模式(Structural Design Patterns) 旨在通过组合类和对象来形成更大结构,从而实现新的功能或简化复杂系统的设计。Golang 中常见的结构型设计模式包括 适配器模式(Adapter Pattern)桥接模式(Bridge Pattern)装饰器模式(Decorator Pattern)外观模式(Facade Pattern)享元模式(Flyweight Pattern)组合设计模式(Composite Pattern)代理模式(Proxy Pattern) 等。

3.2 适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern) 将一个类的接口转换为客户希望的另一个接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。适配器模式主要用于解决新旧系统(或新旧接口)不兼容的问题,使得现有的类可以在不修改其源码的情况下与其他类协同工作,这样原本由于接口不兼容而无法工作的类可以一起工作。

在Golang中,适配器模式可以通过组合或接口实现。

具体的示例:组合阿里支付、微信支付 提供支付服务。

步骤如下:

  • 定义阿里支付接口 type AliPayInterface interface,包含Pay方法
  • 定义微信支付接口 type WeChatPayInterface interface,包含Pay方法
  • 分别定义阿里支付和微信支付的实现类 AliPay 和 WeChatPay struct
  • 定义目标接口 type TargetPayInterface interface,包含DealPay方法
  • 定义TargetPayInterface接口实现类 PayAdapter struct,实现DealPay方法
  • 内部持有AliPayInterface和WeChatPayInterface对象,根据类型分别调用其Pay方法

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// file: pay.go

package pay

import (
    "fmt"
)

// AlipayInterface 支付宝SDK
type AlipayInterface interface {
	Pay(money int)
}

type AlipayPay struct {
}

func (a *AlipayPay) Pay(money int) {
	fmt.Println("这里是支付宝支付:", "费用是:", money)
}

type WeixinPayInterface interface {
	Pay(money int)
}
type WeixinPay struct {
}

func (a *WeixinPay) Pay(money int) {
	fmt.Println("这里是微信支付:", "费用是:", money)
}

// TargetPayInterface 目标接口,能支持传入支付宝或者微信支付进行支付
type TargetPayInterface interface {
	DealPay(payType string, money int)
}

// 自己的adapter,实现微信和支付宝支付
type NewAdapter struct {
	AlipayInterface
	WeixinPayInterface
}

func (n *NewAdapter) DealPay(payType string, money int) {
	switch payType {
	case "weixinpay":
		n.WeixinPayInterface.Pay(money)
	case "alipay":
		n.AlipayInterface.Pay(money)
	default:
		fmt.Println("不支持的支付方式")
	}
}

// file: pay_test.go

package pay

import (
    "testing"
)

func TestCarAdapterPay(t *testing.T) {
    // 同时调用支付宝和微信支付
	t := &NewAdapter{
		AlipayInterface:    &AlipayPay{},
		WeixinPayInterface: &WeixinPay{},
	}
	// 这里业务中基于一个用户同时只能调用一种支付方式。
	t.DealPay("weixinpay", 35)
	t.DealPay("alipay", 101)
}

3.3 桥接模式(Bridge Pattern)

桥接模式(Bridge Pattern) 通过组合方式将抽象部分与它的具体实现部分分离,使得两部分独立扩展。桥接模式通过提供一种分离抽象和实现的方法,帮助我们应对系统的复杂性,提高代码的可扩展性和灵活性。

在 Golang 中,我们可以通过接口和结构体来实现桥接模式。

下面是一个详细的示例,展示如何在 Golang 中使用桥接模式:

假设我们要设计一个图形绘制系统,其中图形有不同的形状(例如圆形、矩形)和不同的颜色(例如红色、绿色)。我们希望通过桥接模式,使得形状和颜色可以独立变化。

实现步骤:

  1. 定义颜色接口:
  • Color 接口定义了一个 Fill 方法,用于填充颜色。
  • Red 和 Green 结构体实现 Color 接口,分别表示红色和绿色。
  1. 定义形状抽象类:
  • Shape 抽象类包含一个 Color 接口类型的字段和一个 Draw 方法。
  • Circle 和 Rectangle 结构体嵌入 Shape 抽象类,分别表示圆形和矩形。

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// file: draw.go

import (
    "fmt"
)

// Color 是颜色接口,定义了填充颜色的方法
type Color interface {
    Fill()
}

// Red 是具体颜色类,表示红色
type Red struct{}

func (r *Red) Fill() {
    fmt.Println("Filling with red color")
}

// Green 是具体颜色类,表示绿色
type Green struct{}

func (g *Green) Fill() {
    fmt.Println("Filling with green color")
}

// Shape 是形状抽象类,包含一个 Color 接口类型的字段
type Shape struct {
    color Color
}

func (s *Shape) SetColor(color Color) {
    s.color = color
}

func (s *Shape) Draw() {
    // 具体形状类将会实现此方法
}

// Circle 是具体形状类,表示圆形
type Circle struct {
    Shape
}

func (c *Circle) Draw() {
    fmt.Print("Drawing Circle - ")
    c.color.Fill()
}

// Rectangle 是具体形状类,表示矩形
type Rectangle struct {
    Shape
}

func (r *Rectangle) Draw() {
    fmt.Print("Drawing Rectangle - ")
    r.color.Fill()
}

// file: draw_test.go

import (
    "testing"
)

func TestCarAdapterPay(t *testing.T) {
    // 使用桥接模式绘制图形
    red := &Red{}
    green := &Green{}

    circle := &Circle{}
    circle.SetColor(red)
    circle.Draw()

    rectangle := &Rectangle{}
    rectangle.SetColor(green)
    rectangle.Draw()

    // 改变颜色
    circle.SetColor(green)
    circle.Draw()
}

代码说明:

  • Color 接口:定义了一个 Fill 方法,用于填充颜色。
  • 具体颜色类:Red 和 Green 结构体实现了 Color 接口,分别表示红色和绿色。
  • Shape 抽象类:包含一个 Color 接口类型的字段和一个 Draw 方法。SetColor 方法用于设置颜色。
  • 具体形状类:Circle 和 Rectangle 结构体嵌入 Shape 抽象类,分别实现 Draw 方法,并调用 color.Fill 来填充颜色。
  • 客户端代码:在 main 函数中,创建了 Circle 和 Rectangle 对象,并通过 SetColor 方法设置颜色,然后调用 Draw 方法绘制图形。

优点:

  • 分离抽象和实现:桥接模式将抽象部分和实现部分分离,使它们可以独立地变化。
  • 提高系统扩展性:可以独立地扩展抽象部分和实现部分,而不会相互影响。
  • 遵循开闭原则:通过增加新的具体实现类或具体抽象类,来扩展系统,而无需修改现有代码。

适用场景:

  • 系统需要在抽象化和具体化之间增加更多的灵活性。
  • 一个类存在两个或多个独立变化的维度,且需要独立地扩展。
  • 通过桥接模式可以避免在继承层次结构中添加多个子类。

3.4 装饰器模式(Decorator Pattern)

装饰器模式(Decorator Pattern)允许在不改变对象接口的情况下,动态地给对象添加功能。装饰器模式通过将对象放入一个封装对象中来实现,这个封装对象在调用原有对象的基础上增强其功能。

在 Golang 中,装饰器模式可以通过接口和组合来实现。下面是一个详细的示例,展示如何在 Golang 中使用装饰器模式。

示例场景: 假设我们有一个简单的通知系统,可以发送基本的通知。现在我们希望在不修改原有通知系统代码的情况下,添加更多的功能,比如发送邮件通知和短信通知。

  1. 定义通知接口: Notifier 接口包含一个 Send 方法,用于发送通知。

  2. 实现基本通知类: BasicNotifier 实现了 Notifier 接口,提供基本的通知功能。

  3. 实现装饰器基类: NotifierDecorator 实现了 Notifier 接口,包含一个 Notifier 类型的字段,作为被装饰的对象。

  4. 实现具体装饰器类: EmailNotifier 和 SMSNotifier 继承 NotifierDecorator,分别提供发送邮件通知和短信通知的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// file: notifier.go

import (
    "fmt"
)

// Notifier 是通知接口,定义了发送通知的方法
type Notifier interface {
    Send(message string)
}

// BasicNotifier 是基本通知类,提供基本的通知功能
type BasicNotifier struct{}

func (n *BasicNotifier) Send(message string) {
    fmt.Println("Sending basic notification:", message)
}

// NotifierDecorator 是装饰器基类,实现了 Notifier 接口
type NotifierDecorator struct {
    notifier Notifier
}

func (d *NotifierDecorator) Send(message string) {
    d.notifier.Send(message)
}

// EmailNotifier 是具体装饰器类,提供发送邮件通知的功能
type EmailNotifier struct {
    NotifierDecorator
}

func NewEmailNotifier(notifier Notifier) *EmailNotifier {
    return &EmailNotifier{
        NotifierDecorator{notifier: notifier},
    }
}

func (e *EmailNotifier) Send(message string) {
    e.notifier.Send(message)
    e.sendEmail(message)
}

func (e *EmailNotifier) sendEmail(message string) {
    fmt.Println("Sending email notification:", message)
}

// SMSNotifier 是具体装饰器类,提供发送短信通知的功能
type SMSNotifier struct {
    NotifierDecorator
}

func NewSMSNotifier(notifier Notifier) *SMSNotifier {
    return &SMSNotifier{
        NotifierDecorator{notifier: notifier},
    }
}

func (s *SMSNotifier) Send(message string) {
    s.notifier.Send(message)
    s.sendSMS(message)
}

func (s *SMSNotifier) sendSMS(message string) {
    fmt.Println("Sending SMS notification:", message)
}


// file: notifier_test.go

import (
    "testing"
)

func TestNotifier(t *testing.T) {
    // 使用装饰器模式发送通知
    basicNotifier := &BasicNotifier{}

    emailNotifier := NewEmailNotifier(basicNotifier)
    smsNotifier := NewSMSNotifier(emailNotifier)

    fmt.Println("Sending notifications with email and SMS:")
    smsNotifier.Send("Hello, Decorator Pattern!")
}

代码说明:

  • Notifier 接口:定义了一个 Send 方法,用于发送通知。
  • BasicNotifier 类:实现了 Notifier 接口,提供基本的通知功能。
  • NotifierDecorator 类:实现了 Notifier 接口,包含一个 Notifier 类型的字段作为被装饰的对象,并在 Send 方法中调用被装饰对象的 Send 方法。
  • EmailNotifier 类:继承 NotifierDecorator,在 Send 方法中先调用被装饰对象的 Send 方法,然后调用 sendEmail 方法发送邮件通知。
  • SMSNotifier 类:继承 NotifierDecorator,在 Send 方法中先调用被装饰对象的 Send 方法,然后调用 sendSMS 方法发送短信通知。
  • 客户端代码:在 main 函数中,创建了 BasicNotifier 对象,并通过 EmailNotifier 和 SMSNotifier 装饰器增强其功能,最后发送带有邮件和短信通知的消息。

优点

  • 灵活性高:可以在运行时动态地添加或删除对象的功能。
  • 遵循开闭原则:可以通过增加新的装饰器类来扩展对象的功能,而不修改现有代码。
  • 单一职责原则:可以将各个装饰功能分离到不同的类中,使代码更易于维护。

适用场景

  • 需要在不改变原有对象的情况下,动态地给对象添加功能。
  • 需要扩展类的功能,且这些功能可以彼此独立以及按不同的顺序组合。
  • 希望通过组合不同的装饰器来获得功能更强大的对象。

3.5 外观模式(Facade Pattern)

外观模式(Facade Pattern) 为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。外观模式通过引入一个外观类,为复杂的子系统提供一个简单的接口,从而减少客户端与子系统之间的耦合。

在 Golang 中,我们可以通过定义一个外观结构体,将复杂子系统的功能封装在其中,对外提供简单的方法接口。下面是一个详细的示例,展示如何在 Golang 中使用外观模式。

示例场景: 假设我们有一个复杂的家庭影院系统,包括 DVD 播放器、投影仪、音响和灯光系统。我们希望通过一个统一的接口来控制整个家庭影院系统。

1。 定义子系统类: DvdPlayer、Projector、SoundSystem 和 Lights 表示家庭影院系统的各个子系统。

  1. 实现外观类: HomeTheaterFacade 类提供了统一的方法接口,用于控制家庭影院系统的各个子系统。

示例代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
// file: home_theater_facade.go
package home_theater_facade

import "fmt"

// 子系统类:DvdPlayer
type DvdPlayer struct{}

func (d *DvdPlayer) On() {
    fmt.Println("DVD Player is on")
}

func (d *DvdPlayer) Play(movie string) {
    fmt.Printf("Playing movie: %s\n", movie)
}

func (d *DvdPlayer) Off() {
    fmt.Println("DVD Player is off")
}

// 子系统类:Projector
type Projector struct{}

func (p *Projector) On() {
    fmt.Println("Projector is on")
}

func (p *Projector) Off() {
    fmt.Println("Projector is off")
}

// 子系统类:SoundSystem
type SoundSystem struct{}

func (s *SoundSystem) On() {
    fmt.Println("Sound System is on")
}

func (s *SoundSystem) SetVolume(volume int) {
    fmt.Printf("Sound System volume set to %d\n", volume)
}

func (s *SoundSystem) Off() {
    fmt.Println("Sound System is off")
}

// 子系统类:Lights
type Lights struct{}

func (l *Lights) Dim() {
    fmt.Println("Lights are dimmed")
}

func (l *Lights) On() {
    fmt.Println("Lights are on")
}

// 外观类:HomeTheaterFacade
type HomeTheaterFacade struct {
    dvdPlayer   *DvdPlayer
    projector   *Projector
    soundSystem *SoundSystem
    lights      *Lights
}

func NewHomeTheaterFacade(dvdPlayer *DvdPlayer, projector *Projector, soundSystem *SoundSystem, lights *Lights) *HomeTheaterFacade {
    return &HomeTheaterFacade{
        dvdPlayer:   dvdPlayer,
        projector:   projector,
        soundSystem: soundSystem,
        lights:      lights,
    }
}

func (h *HomeTheaterFacade) WatchMovie(movie string) {
    fmt.Println("Get ready to watch a movie...")
    h.lights.Dim()
    h.projector.On()
    h.soundSystem.On()
    h.soundSystem.SetVolume(10)
    h.dvdPlayer.On()
    h.dvdPlayer.Play(movie)
}

func (h *HomeTheaterFacade) EndMovie() {
    fmt.Println("Shutting movie theater down...")
    h.dvdPlayer.Off()
    h.soundSystem.Off()
    h.projector.Off()
    h.lights.On()
}

// file: home_theater_facade_test.go

import (
    "testing"
)

func TestNotifier(t *testing.T) {
    // 客户端代码
    dvdPlayer := &DvdPlayer{}
    projector := &Projector{}
    soundSystem := &SoundSystem{}
    lights := &Lights{}

    homeTheater := NewHomeTheaterFacade(dvdPlayer, projector, soundSystem, lights)

    homeTheater.WatchMovie("Inception")
    t.Log("Watching Movie...")
    homeTheater.EndMovie()
}

代码解释

  1. 子系统类:
  • DvdPlayer、Projector、SoundSystem 和 Lights 类分别代表家庭影院系统的各个子系统,它们各自有自己的操作方法。
  1. 外观类: HomeTheaterFacade 类包含各个子系统的实例,并提供了 WatchMovie 和 EndMovie 方法。WatchMovie 方法依次调用各个子系统的方法来准备观看电影,EndMovie 方法依次调用各个子系统的方法来关闭家庭影院系统。

  2. 客户端代码: 在 main 函数中,创建了各个子系统的实例,并将它们传递给 HomeTheaterFacade 的构造函数。然后,通过调用 WatchMovie 和 EndMovie 方法,控制家庭影院系统的启动和关闭。

优点

  • 简化接口:外观模式提供了一个简单的接口,简化了客户端与复杂子系统的交互。
  • 减少耦合:客户端代码与子系统的具体实现解耦,通过外观类与子系统进行交互。
  • 更好的维护性:更改子系统的实现不会影响客户端代码,只需修改外观类即可。

适用场景

  • 当需要为一个复杂子系统提供一个简单的接口时。
  • 当需要解耦客户端代码与复杂子系统的实现时。
  • 当需要将子系统的多个操作组合成一个高级操作时。

通过外观模式,我们可以为复杂系统提供一个简单的接口,使得系统更加易用,同时提高代码的可维护性和可扩展性。

3.6 享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern) 通过共享大量细粒度对象来减少内存消耗。享元模式特别适用于那些数量巨大的对象,这些对象的部分状态是可以共享的。享元模式可以通过将共享部分(内部状态)和不共享部分(外部状态)分离来实现。

在 Golang 中,我们可以通过使用工厂模式来管理享元对象的创建和共享。下面是一个详细的示例,展示如何在 Golang 中使用享元模式。

示例场景: 假设我们要开发一个图形应用程序,其中包含大量的圆形对象。每个圆形的颜色是可以共享的,但位置(x 和 y 坐标)是不同的。我们希望通过享元模式来减少内存消耗。

  1. 定义享元对象接口: Circle 接口包含一个 Draw 方法,用于绘制圆形。

  2. 实现具体享元对象: ConcreteCircle 结构体实现了 Circle 接口,包含共享的颜色和不共享的位置。

  3. 实现享元工厂: CircleFactory 负责创建和管理享元对象,通过颜色来查找现有的圆形对象,如果不存在则创建新的对象。

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// file: circle.go

import (
    "fmt"
)

// Circle 是享元对象接口,定义了绘制方法
type Circle interface {
    Draw()
}

// ConcreteCircle 是具体享元对象,包含共享的颜色和不共享的位置
type ConcreteCircle struct {
    color string
    x, y  int
}

func (c *ConcreteCircle) Draw() {
    fmt.Printf("Drawing Circle [Color: %s, x: %d, y: %d]\n", c.color, c.x, c.y)
}

// CircleFactory 是享元工厂,负责创建和管理享元对象
type CircleFactory struct {
    circleMap map[string]*ConcreteCircle
}

func NewCircleFactory() *CircleFactory {
    return &CircleFactory{
        circleMap: make(map[string]*ConcreteCircle),
    }
}

func (f *CircleFactory) GetCircle(color string) *ConcreteCircle {
    if circle, exists := f.circleMap[color]; exists {
        return circle
    }
    circle := &ConcreteCircle{color: color}
    f.circleMap[color] = circle
    return circle
}


// file: circle_.go

import (
    "testing"
)

func TestCircle(t *testing.T) {
    factory := NewCircleFactory()

    redCircle1 := factory.GetCircle("red")
    redCircle1.x = 10
    redCircle1.y = 20
    redCircle1.Draw()

    redCircle2 := factory.GetCircle("red")
    redCircle2.x = 30
    redCircle2.y = 40
    redCircle2.Draw()

    blueCircle := factory.GetCircle("blue")
    blueCircle.x = 50
    blueCircle.y = 60
    blueCircle.Draw()

    t.Log("Red Circle 1 and Red Circle 2 are the same instance:", redCircle1 == redCircle2)
}

代码解释:

  • Circle 接口:定义了一个 Draw 方法,用于绘制圆形。
  • ConcreteCircle 结构体:实现了 Circle 接口,包含共享的颜色属性和不共享的 x、y 坐标。
  • CircleFactory 工厂:负责创建和管理享元对象,通过颜色查找现有的圆形对象,如果不存在则创建新的对象。circleMap 用于存储已经创建的享元对象。
  • 客户端代码:
    • 在 main 函数中,首先创建了 CircleFactory 实例。
    • 然后通过 GetCircle 方法获取圆形对象,并设置其位置。
    • 调用 Draw 方法绘制圆形对象。
    • 通过比较 redCircle1 和 redCircle2 可以看出,相同颜色的圆形对象是共享的同一个实例。

优点

  • 内存优化:通过共享对象,减少了内存消耗,尤其适用于大量相似对象的场景。
  • 提高性能:减少了对象创建的开销,提升了系统性能。

缺点

  • 复杂性增加:引入了工厂和共享管理逻辑,增加了系统的复杂性。
  • 非线程安全:如果在多线程环境下使用享元模式,需要考虑线程安全问题。

适用场景

  • 系统中存在大量相似对象,且这些对象的部分状态是可以共享的。
  • 需要通过共享技术来减少内存消耗和提高性能。

通过享元模式,我们可以有效地减少内存消耗,提高系统性能,特别是在大量细粒度对象的场景下。

3.7 组合设计模式(Composite Pattern)

组合模式(Composite Pattern) 允许将对象组合成树状结构以表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。

在 Golang 中,我们可以通过接口和结构体来实现组合模式。下面是一个详细的示例,展示如何在 Golang 中使用组合模式。

示例场景:假设我们要设计一个文件系统,文件系统中有文件和目录。文件和目录都可以被当作节点处理,目录可以包含文件和其他目录。我们希望通过组合模式来实现这个文件系统,使得文件和目录的操作具有一致性。

  1. 定义组件接口:
  • Component 接口包含 Display 方法,用于显示文件或目录的名称。
  1. 实现叶子节点:
  • File 结构体实现了 Component 接口,表示文件节点。
  1. 实现组合节点:
  • Directory 结构体实现了 Component 接口,表示目录节点,可以包含多个子节点(文件或目录)。

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// file: filesystem.go

import (
    "fmt"
)

// Component 是组件接口,定义了 Display 方法
type Component interface {
    Display(indentation string)
}

// File 是叶子节点,表示文件
type File struct {
    name string
}

func (f *File) Display(indentation string) {
    fmt.Println(indentation + f.name)
}

// Directory 是组合节点,表示目录
type Directory struct {
    name      string
    components []Component
}

func (d *Directory) Display(indentation string) {
    fmt.Println(indentation + d.name)
    for _, component := range d.components {
        component.Display(indentation + "  ")
    }
}

func (d *Directory) Add(component Component) {
    d.components = append(d.components, component)
}

// file: filesystem_.go

import (
    "testing"
)

func TestCircle(t *testing.T) {
    // 使用组合模式构建文件系统
    file1 := &File{name: "file1.txt"}
    file2 := &File{name: "file2.txt"}
    file3 := &File{name: "file3.txt"}

    dir1 := &Directory{name: "dir1"}
    dir2 := &Directory{name: "dir2"}

    dir1.Add(file1)
    dir1.Add(file2)
    dir2.Add(file3)
    dir1.Add(dir2)

    fmt.Println("File System Structure:")
    dir1.Display("")
}

代码解释

  • Component 接口:定义了一个 Display 方法,用于显示组件(文件或目录)的名称。
  • File 结构体:实现了 Component 接口,表示文件节点,在 Display 方法中显示文件的名称。
  • Directory 结构体:实现了 Component 接口,表示目录节点,包含一个 components 切片,用于存储子节点。在 Display 方法中显示目录的名称,并递归显示其子节点的名称。Add 方法用于向目录中添加子节点。
  • 客户端代码:
    • 在 main 函数中,创建了几个文件和目录,并使用 Add 方法将文件和目录添加到合适的位置,最终调用 Display 方法显示整个文件系统的结构。

优点 层次结构:组合模式可以很自然地描述对象的层次结构,特别是树形结构。 一致性:组合模式使得客户端可以一致地使用组合对象和单个对象。 可扩展性:可以很容易地增加新的组件类型,比如新的文件类型或目录类型。

缺点 复杂性增加:在某些情况下,可能会引入过多的抽象层次,使得系统复杂度增加。 性能问题:对于包含大量对象的组合结构,可能会引起性能问题,需要优化。

适用场景 需要表示对象的部分-整体层次结构。 希望客户端统一处理组合对象和单个对象。 需要动态地构建复杂的对象结构。

通过组合模式,我们可以有效地构建和管理具有层次结构的复杂对象,使得代码更易于扩展和维护。

3.8 代理模式(Proxy Pattern)

代理模式(Proxy Pattern) 为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,可以在不改变目标对象代码的情况下添加额外的功能,比如控制访问权限、延迟加载、日志记录等。

在 Golang 中,我们可以通过接口和结构体来实现代理模式。下面是一个详细的示例,展示如何在 Golang 中使用代理模式。

示例场景: 假设我们有一个非常耗时的操作,比如从数据库中加载用户信息。为了优化性能,我们希望在第一次访问用户信息时加载数据,并在后续访问时返回缓存的数据。

  1. 定义用户服务接口: UserService 接口包含一个 GetUser 方法,用于获取用户信息。

  2. 实现真实用户服务: RealUserService 结构体实现了 UserService 接口,模拟从数据库加载用户信息。

  3. 实现代理用户服务: UserServiceProxy 结构体实现了 UserService 接口,包含一个 RealUserService 实例,用于代理对真实用户服务的访问,并添加缓存功能。

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// file: user.go
import (
    "fmt"
    "time"
)

// User 是用户信息结构体
type User struct {
    ID   int
    Name string
}

// UserService 是用户服务接口,定义了获取用户信息的方法
type UserService interface {
    GetUser(id int) *User
}

// RealUserService 是真实用户服务,实现了 UserService 接口
type RealUserService struct{}

func (s *RealUserService) GetUser(id int) *User {
    // 模拟从数据库加载数据
    fmt.Println("Loading user from database...")
    time.Sleep(2 * time.Second) // 模拟耗时操作
    return &User{ID: id, Name: "John Doe"}
}

// UserServiceProxy 是用户服务代理,实现了 UserService 接口
type UserServiceProxy struct {
    realService *RealUserService
    cache       map[int]*User
}

func NewUserServiceProxy(realService *RealUserService) *UserServiceProxy {
    return &UserServiceProxy{
        realService: realService,
        cache:       make(map[int]*User),
    }
}

func (p *UserServiceProxy) GetUser(id int) *User {
    if user, exists := p.cache[id]; exists {
        fmt.Println("Returning cached user...")
        return user
    }
    user := p.realService.GetUser(id)
    p.cache[id] = user
    return user
}

// file: user_test.go

import (
    "testing"
)

func TestUser(t *testing.T) {
    realService := &RealUserService{}
    proxy := NewUserServiceProxy(realService)

    // 第一次访问,加载用户数据
    user := proxy.GetUser(1)
    t.Logf("User: %+v\n", user)

    // 第二次访问,返回缓存数据
    user = proxy.GetUser(1)
    t.Logf("User: %+v\n", user)
}

解释

  • User 结构体:表示用户信息,包括用户ID和姓名。
  • UserService 接口:定义了一个 GetUser 方法,用于获取用户信息。
  • RealUserService 结构体:实现了 UserService 接口,模拟从数据库加载用户信息。在 GetUser 方法中,通过 time.Sleep 模拟耗时操作。
  • UserServiceProxy 结构体:实现了 UserService 接口,包含一个 RealUserService 实例和一个缓存,用于存储已经加载的用户信息。在 GetUser 方法中,首先检查缓存中是否存在用户信息,如果存在则直接返回缓存的数据,否则调用 RealUserService 的 GetUser 方法加载用户信息,并将其存储在缓存中。
  • 客户端代码:
    • 在 main 函数中,创建了 RealUserService 和 UserServiceProxy 实例。
    • 通过代理对象 proxy 调用 GetUser 方法,第一次访问时加载用户数据,第二次访问时返回缓存数据。

优点

  • 控制访问:代理模式可以控制对目标对象的访问,可以用于权限控制、延迟加载等场景。
  • 性能优化:通过添加缓存等机制,可以提高系统性能,减少不必要的资源消耗。
  • 解耦代码:客户端代码与目标对象的实现细节解耦,通过代理对象进行访问,方便后续扩展和维护。

缺点

  • 复杂性增加:引入代理对象,会增加系统的复杂性,需要额外处理代理逻辑。
  • 性能开销:代理模式本身会引入一些额外的性能开销,特别是在代理逻辑较为复杂时。

适用场景

  • 需要控制对目标对象的访问时,比如权限控制、远程代理、虚拟代理等。
  • 需要在访问目标对象前后添加额外操作时,比如日志记录、性能监控等。
  • 需要通过缓存机制优化性能,减少不必要的资源消耗时。

通过代理模式,我们可以有效地控制对目标对象的访问,并在访问前后添加额外的操作,从而提高系统的灵活性和可维护性。

四、行为型设计模式