golang24种设计模式——结构型模式
结构型模式(Structural Pattern),它主要关注如何将类或对象组合成更大的结构,以便在不改变原有类或对象的情况下,实现新的功能或优化系统结构。 结构型模式的核心思想是通过组合(Comp

golang24种设计模式——结构型模式

发布时间:2025-01-13 (2025-01-13)

结构型模式(Structural Pattern),它主要关注如何将类或对象组合成更大的结构,以便在不改变原有类或对象的情况下,实现新的功能或优化系统结构。

结构型模式的核心思想是通过组合(Composition)而不是继承(Inheritance)来实现代码的复用和扩展。它们帮助开发者设计出灵活、可扩展的系统结构,同时降低类与类之间的耦合度。

代理模式 Proxy

它通过提供一个代理对象来控制对另一个对象的访问。代理模式的核心思想是在不改变原始对象的情况下,通过代理对象来增强或限制对原始对象的访问。

代理模式通常用于以下场景:

  • 延迟初始化:当对象的创建成本较高时,可以通过代理延迟对象的初始化。
  • 访问控制:通过代理对象限制对原始对象的访问权限。
  • 日志记录:通过代理对象记录对原始对象的访问日志。
  • 缓存:通过代理对象缓存原始对象的结果,避免重复计算。
package main

import (
  "fmt"
  "time"
)

// 主题接口
type Subject interface {
  Request()
}

// 真实对象
type RealSubject struct{}

func (r *RealSubject) Request() {
  fmt.Println("RealSubject: Handling Request")
}

// 代理对象
type Proxy struct {
  realSubject *RealSubject
}

func NewProxy() *Proxy {
  return &Proxy{}
}

func (p *Proxy) Request() {
  // 延迟初始化真实对象
  if p.realSubject == nil {
    fmt.Println("Proxy: Lazy Initializing RealSubject")
    p.realSubject = &RealSubject{}
  }

  // 访问控制
  fmt.Println("Proxy: Checking Access")
  time.Sleep(1 * time.Second) // 模拟访问控制逻辑

  // 调用真实对象的方法
  p.realSubject.Request()

  // 日志记录
  fmt.Println("Proxy: Logging Request")
}

// 客户端代码
func main() {
  proxy := NewProxy()

  // 通过代理对象访问真实对象
  proxy.Request()
}

桥接模式 Bridge

它的核心思想是将抽象部分与实现部分分离,使它们可以独立变化。通过这种方式,桥接模式能够避免类的数量爆炸(即类的组合呈指数增长),同时提高代码的可扩展性和可维护性。

例如:

假设你有两台电脑: 一台 Mac 和一台 Windows。 还有两台打印机: 爱普生和惠普。 这两台电脑和打印机可能会任意组合使用。

刚开始功能会这样写,windows组合爱普生和惠普,mac组合爱普生和惠普,打印就调用自己的print方法

// Mac + Epson
type MacEpson struct{}

func (m *MacEpson) Print() {
    fmt.Println("Print request for mac")
    fmt.Println("Printing by a EPSON Printer")
}

// Mac + HP
type MacHp struct{}

func (m *MacHp) Print() {
    fmt.Println("Print request for mac")
    fmt.Println("Printing by a HP Printer")
}

// Windows + Epson
type WindowsEpson struct{}

func (w *WindowsEpson) Print() {
    fmt.Println("Print request for windows")
    fmt.Println("Printing by a EPSON Printer")
}

// Windows + HP
type WindowsHp struct{}

func (w *WindowsHp) Print() {
    fmt.Println("Print request for windows")
    fmt.Println("Printing by a HP Printer")
}

但是,如果引入新的打印机, 代码量会成倍增长。

所以, 我们需要把计算机和打印机解耦,计算机的print的方法,就桥接到选择的打印机的print方法上

代码如下:

package main

import "fmt"

// 1. 定义 Printer 接口(实现层)
type Printer interface {
  PrintFile()
}

// 2. 定义 Epson 打印机(具体实现)
type Epson struct{}

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

// 3. 定义 HP 打印机(具体实现)
type Hp struct{}

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

// 4. 定义 Computer 接口(抽象层)
type Computer interface {
  Print()
  SetPrinter(Printer)
}

// 5. 定义 Mac 计算机(精确抽象)
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
}

// 6. 定义 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
}

// 7. 客户端代码
func main() {
  // 创建打印机实例
  hpPrinter := &Hp{}
  epsonPrinter := &Epson{}

  // 创建计算机实例
  macComputer := &Mac{}
  winComputer := &Windows{}

  // Mac 使用 HP 打印机
  macComputer.SetPrinter(hpPrinter)
  macComputer.Print()
  fmt.Println()

  // Mac 使用 Epson 打印机
  macComputer.SetPrinter(epsonPrinter)
  macComputer.Print()
  fmt.Println()

  // Windows 使用 HP 打印机
  winComputer.SetPrinter(hpPrinter)
  winComputer.Print()
  fmt.Println()

  // Windows 使用 Epson 打印机
  winComputer.SetPrinter(epsonPrinter)
  winComputer.Print()
  fmt.Println()
}

桥接模式的优点

  1. 解耦:
    • 抽象部分和实现部分可以独立变化,互不影响。
    • 修改实现部分不会影响抽象部分的代码。
  2. 灵活性:
    • 可以动态地切换实现部分(例如,运行时更换实现)。
  3. 可扩展性:
    • 新增抽象部分或实现部分时,不需要修改现有代码。
  4. 避免类爆炸:
    • 通过组合代替继承,避免了类的数量呈指数增长。

组合模式 Composite

它允许你将对象组合成树形结构来表示“部分-整体”的层次关系。组合模式让客户端可以统一地处理单个对象和对象组合。

应用场景:

  1. 文件系统:如目录和文件的管理,可以通过组合模式将文件夹视为组合节点,文件视为叶子节点。
  2. 组织结构:如公司内部的部门和员工关系,可以通过组合模式将部门视为组合节点,员工视为叶子节点。
package main

import (
  "fmt"
)

// FileSystemNode 组件接口:文件系统节点
type FileSystemNode interface {
  Display(indent string)
}

// File 叶子节点:文件
type File struct {
  name string
}

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

// Folder 组合节点:文件夹
type Folder struct {
  name     string
  children []FileSystemNode
}

func (f *Folder) Display(indent string) {
  fmt.Println(indent + f.name)
  for _, child := range f.children {
    child.Display(indent + "  ")
  }
}

func (f *Folder) Add(child FileSystemNode) {
  f.children = append(f.children, child)
}

// 客户端代码
func main() {
  // 创建文件
  file1 := &File{name: "file1.txt"}
  file2 := &File{name: "file2.txt"}
  file3 := &File{name: "file3.txt"}

  // 创建文件夹
  folder1 := &Folder{name: "Folder1"}
  folder2 := &Folder{name: "Folder2"}

  // 将文件添加到文件夹
  folder1.Add(file1)
  folder1.Add(file2)
  folder2.Add(file3)

  // 将文件夹添加到另一个文件夹
  rootFolder := &Folder{name: "Root"}
  rootFolder.Add(folder1)
  rootFolder.Add(folder2)

  // 显示文件系统结构
  rootFolder.Display("")
}

为什么使用组合模式?

  1. 简化客户端代码:客户端可以一致地对待单个对象和对象组合,而不需要关心它们的具体类型。
  2. 增强灵活性:可以在不修改现有代码的情况下轻松添加新的组件或修改现有组件的结构。
  3. 提高可扩展性:支持递归组合,使得复杂的层次结构易于构建和维护。

装饰器模式 Decorator

它允许你动态地为对象添加行为或职责,而不需要修改对象的原始类。通过引入装饰者类,可以在运行时灵活地组合不同的功能,而不需要创建大量的子类。装饰者模式的核心思想是将对象包装在一个或多个装饰者中,每个装饰者都可以在调用被装饰对象的方法之前或之后添加额外的行为。

假设你正在开发一个 Web 服务,其中有一个核心功能是处理用户请求。现在,你需要在不修改核心功能代码的情况下,为请求处理添加以下功能:

  1. 日志记录:记录每个请求的详细信息。
  2. 性能监控:记录每个请求的处理时间。

使用装饰器模式,你可以轻松地实现这些功能,而无需修改原始请求处理逻辑。

package main

import (
  "fmt"
  "time"
)

// RequestHandler 组件接口:请求处理器
type RequestHandler interface {
  HandleRequest(url string) string
}

// CoreRequestHandler 具体组件:核心请求处理器
type CoreRequestHandler struct{}

func (c *CoreRequestHandler) HandleRequest(url string) string {
  // 模拟请求处理
  time.Sleep(100 * time.Millisecond)
  return fmt.Sprintf("Response from %s", url)
}

// LoggingDecorator 装饰器:日志记录
type LoggingDecorator struct {
  handler RequestHandler
}

func (l *LoggingDecorator) HandleRequest(url string) string {
  // 记录请求日志
  fmt.Printf("Logging: Handling request for %s\n", url)

  // 调用原始处理器
  response := l.handler.HandleRequest(url)

  // 记录响应日志
  fmt.Printf("Logging: Response for %s: %s\n", url, response)
  return response
}

// PerformanceMonitorDecorator 装饰器:性能监控
type PerformanceMonitorDecorator struct {
  handler RequestHandler
}

func (p *PerformanceMonitorDecorator) HandleRequest(url string) string {
  // 记录开始时间
  start := time.Now()

  // 调用原始处理器
  response := p.handler.HandleRequest(url)

  // 记录处理时间
  duration := time.Since(start)
  fmt.Printf("Performance: Request for %s took %v\n", url, duration)
  return response
}

// 客户端代码
func main() {
  // 创建核心请求处理器
  coreHandler := &CoreRequestHandler{}

  // 添加日志记录功能
  loggingHandler := &LoggingDecorator{coreHandler}

  // 添加性能监控功能
  monitoredHandler := &PerformanceMonitorDecorator{loggingHandler}

  // 处理请求
  response := monitoredHandler.HandleRequest("/api/data")
  fmt.Println(response)
}

装饰器模式的优势

  1. 动态扩展功能:
    • 你可以在运行时为请求处理器添加日志记录和性能监控功能,而无需修改核心请求处理器的代码。
  2. 单一职责原则:
    • 每个装饰器只负责一个特定的功能(如日志记录或性能监控),符合单一职责原则。
  3. 灵活性:
    • 可以轻松地添加或移除装饰器,而不会影响其他部分的代码。

适配器模式 Adapter

它允许不兼容的接口之间进行协作。适配器模式的核心思想是将一个类的接口转换成客户端期望的另一个接口,从而使原本不兼容的类能够一起工作。

假设你的系统需要集成一个第三方支付系统,但其接口与你的系统不兼容。你可以使用适配器模式将第三方支付系统的接口转换为你的系统期望的接口。

package main

import "fmt"

// PaymentSystem 目标接口:支付系统
type PaymentSystem interface {
  Pay(amount float64) bool
}

// LocalPaymentSystem 具体目标:本地支付系统
type LocalPaymentSystem struct{}

func (l *LocalPaymentSystem) Pay(amount float64) bool {
  fmt.Printf("Paid %.2f using local payment system\n", amount)
  return true
}

// ThirdPartyPaymentSystem 适配者:第三方支付系统
type ThirdPartyPaymentSystem struct{}

func (t *ThirdPartyPaymentSystem) MakePayment(amount float64) string {
  return fmt.Sprintf("Paid %.2f using third-party payment system", amount)
}

// PaymentAdapter 适配器:将第三方支付系统适配到本地支付系统接口
type PaymentAdapter struct {
  thirdPartySystem *ThirdPartyPaymentSystem
}

func (p *PaymentAdapter) Pay(amount float64) bool {
  result := p.thirdPartySystem.MakePayment(amount)
  fmt.Println(result)
  return true
}

// 客户端代码
func main() {
  // 使用本地支付系统
  localPayment := &LocalPaymentSystem{}
  localPayment.Pay(100.0)

  // 使用适配器将第三方支付系统适配到本地支付系统接口
  thirdPartyPayment := &ThirdPartyPaymentSystem{}
  adapter := &PaymentAdapter{thirdPartyPayment}
  adapter.Pay(200.0)
}

适配器模式通过一个中间层(适配器)将不兼容的接口转换为兼容的接口,从而使原本不兼容的类能够一起工作。它非常适合用于集成第三方库、复用旧代码或统一接口的场景

外观模式 Facade

它提供了一个统一的接口,用于访问子系统中的一组接口。外观模式的核心思想是简化复杂系统的使用,通过提供一个高层接口,隐藏系统的复杂性,使客户端更容易使用。

假设你正在开发一个电商系统,订单处理涉及多个步骤(如库存检查、支付处理、物流调度等)。你可以使用外观模式将这些步骤封装在一个统一的接口中,简化客户端代码。

package main

import "fmt"

// Inventory 子系统:库存
type Inventory struct{}

func (i *Inventory) Check() {
  fmt.Println("Inventory: Checking stock...")
}

// Payment 子系统:支付
type Payment struct{}

func (p *Payment) Process() {
  fmt.Println("Payment: Processing payment...")
}

// Logistics 子系统:物流
type Logistics struct{}

func (l *Logistics) Schedule() {
  fmt.Println("Logistics: Scheduling delivery...")
}

// OrderFacade 外观:订单处理
type OrderFacade struct {
  inventory *Inventory
  payment   *Payment
  logistics *Logistics
}

func NewOrderFacade() *OrderFacade {
  return &OrderFacade{
    inventory: &Inventory{},
    payment:   &Payment{},
    logistics: &Logistics{},
  }
}

func (o *OrderFacade) PlaceOrder() {
  fmt.Println("Placing order...")
  o.inventory.Check()
  o.payment.Process()
  o.logistics.Schedule()
  fmt.Println("Order placed successfully!")
}

// 客户端代码
func main() {
  orderFacade := NewOrderFacade()
  orderFacade.PlaceOrder()
}

外观模式的关键点

  1. 简化接口:
    • 外观模式通过提供一个简单的接口,隐藏了子系统的复杂性。
  2. 解耦:
    • 客户端代码与子系统解耦,子系统的变化不会影响客户端代码。
  3. 易于使用:
    • 客户端无需了解子系统的内部细节,只需调用外观对象的方法。

享元模式 Flyweight

它通过共享对象来减少内存使用和提高性能。享元模式的核心思想是将对象的共享部分(内部状态)与不可共享部分(外部状态)分离,从而减少重复对象的创建。

举个例子:共享单车和百度网盘中的文件

享元模式的核心思想

  1. 共享对象:
    • 享元模式通过共享相同的内在状态(内部状态)来减少内存使用。
  2. 分离状态:
    • 将对象的状态分为内部状态(可共享)和外部状态(不可共享)。
  3. 工厂管理:
    • 使用工厂模式来管理和复用享元对象。

例如:

在数据库操作中,创建和销毁连接是非常耗时的操作。为了提高性能,通常会使用连接池来管理数据库连接。连接池的核心思想是:

  1. 复用连接:从连接池中获取连接,使用完后将连接释放回连接池,而不是销毁。
  2. 减少开销:避免频繁创建和销毁连接,从而提高性能。
package main

import (
  "fmt"
  "sync"
)

// Connection 享元接口:数据库连接
type Connection interface {
  Execute(query string)
}

// ConcreteConnection 具体享元:数据库连接对象
type ConcreteConnection struct {
  id int
}

func (c *ConcreteConnection) Execute(query string) {
  fmt.Printf("Connection %d executing query: %s\n", c.id, query)
}

// ConnectionPool 享元工厂:连接池
type ConnectionPool struct {
  connections map[int]Connection
  mutex       sync.Mutex
  nextID      int
}

func NewConnectionPool() *ConnectionPool {
  return &ConnectionPool{
    connections: make(map[int]Connection),
    nextID:      1,
  }
}

// GetConnection 获取连接对象
func (p *ConnectionPool) GetConnection() Connection {
  p.mutex.Lock()
  defer p.mutex.Unlock()

  if len(p.connections) > 0 {
    for id, conn := range p.connections {
      delete(p.connections, id)
      return conn
    }
  }

  // 创建新的连接对象
  conn := &ConcreteConnection{id: p.nextID}
  p.nextID++
  return conn
}

// ReleaseConnection 将连接对象释放回连接池,以便其他客户端可以复用这个连接对象
func (p *ConnectionPool) ReleaseConnection(conn Connection) {
  p.mutex.Lock()
  defer p.mutex.Unlock()

  if c, ok := conn.(*ConcreteConnection); ok {
    p.connections[c.id] = c
  }
}

// 客户端代码
func main() {
  pool := NewConnectionPool()

  conn1 := pool.GetConnection()
  conn1.Execute("SELECT * FROM users")
  pool.ReleaseConnection(conn1)

  conn2 := pool.GetConnection()
  conn2.Execute("SELECT * FROM orders")
  pool.ReleaseConnection(conn2)
}

享元模式的关键点

  1. 共享对象:
    • 享元模式通过共享相同的内在状态来减少内存使用。
  2. 分离状态:
    • 将对象的状态分为内部状态(可共享)和外部状态(不可共享)。
  3. 工厂管理:
    • 使用工厂模式来管理和复用享元对象。

参考文档

Go讲解:组合模式 https://blog.csdn.net/zhaoxilengfeng/article/details/144434943

装饰器模式 https://blog.csdn.net/zhaoxilengfeng/article/details/144435005

享元模式 https://blog.csdn.net/zhaoxilengfeng/article/details/144458629