IT评测·应用市场-qidao123.com技术社区
标题:
Go常用的设计模式
[打印本页]
作者:
海哥
时间:
2025-3-28 16:55
标题:
Go常用的设计模式
Go常用的设计模式
常见的设计模式,如
单例模式
、
工厂模式
、
策略模式
、
观察者模式
、
署理模式
、
装饰器模式
和
适配器模式
都可以在 Go 中实现,适用于差别的开辟需求。
这些设计模式不仅能帮助你编写结构清楚、可维护的代码,还能让你更好地应对复杂的编程题目。
一、单例模式(Singleton)
简介:
Go 的单例模式(Singleton Pattern)适用于某些必要确保一个类(或结构体)在整个应用步伐中只有一个实例的场景。通常环境下,单例模式用于全局共享资源、缓存、日记管理、数据库毗连等场景,制止了不必要的对象创建和资源浪费。
使用场景:
配置管理
日记管理
数据毗连池
缓存管理
线程安全的全局状态管理
系统资源管理
优点:
线程安全:使用 sync.Once 可以确保在并发环境下,单例对象只被创建一次,制止竞态条件和数据竞争。
耽误初始化(懒汉式):只有在首次调用时才进行实例化,节省内存和启动时间。
全局唯一性:单例模式可以确保在整个步伐生命周期中只有一个实例,适合必要共享资源的场景(如配置管理、日记记录)。
节省资源:由于实例只创建一次,制止频繁创建和销毁带来的性能瓶颈。
方便访问:单例模式提供一个全局访问点,通过调用同一个函数即可获取实例,方便在整个步伐中使用。
缺点:
全局状态导致代码难以测试:单例模式带来的全局状态使得单元测试难以编写,尤其是必要模拟或更换单例时。
难以扩展:单例模式将创建逻辑硬编码唉单例类中,如果必要更改创建逻辑,往往必要修改核心代码,违背开闭原则。
隐藏依赖性:使用单例时,其他模块可能隐式依赖于单例对象,在修改单例时容易引发不可预期的题目。
倒霉于并行测试:由于单例模式在步伐中只有一个实例,多个测试用例无法并发运行,可能产生数据污染。
潜伏内存泄露:如果单例中持有大量资源且没有及时释放,可能导致内存泄漏。
实现:
package main
import (
"fmt"
"sync"
)
type Singleton struct {
// 可能包含一些属性
}
var instance *Singleton
var once sync.Once
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{}
})
return instance
}
func main() {
s1 := GetInstance()
s2 := GetInstance()
fmt.Println(s1 == s2) // true
}
复制代码
二、工厂模式(Factory)
简介:
工厂模式用于创建对象的实例,但不暴露具体的创建逻辑,它通过接口来解耦对象的创建和使用。
使用场景:
构建复杂对象:如果对象创建过程复杂且有多种变体,使用工厂可以贱货创建逻辑。
接口实例化:当代码中涉及多个实现相同接口的结构体时,使用工厂可以屏蔽具体实现,方便切换。
淘汰重复代码:如果对象创建涉及大量重复逻辑,工厂方法可以封装这些逻辑。
依赖注入(DI)息争耦:使用工厂可以制止代码中直接依赖具体实现类,提升代码灵活性。
面向接口编程:在某昔日场景下必要根据配置或运行时条件来创建差别的实例。
优点:
解耦性高:工厂方法将对象创建与对象使用分离,调用者无需关心对象的具体创建过程。
符合开闭原则:增加新产品时,只需增加新的工厂方法,而无需修改原有代码,淘汰了对现有代码的侵入。
加强代码复用性:工厂方法可以复用已有的创建逻辑,制止代码重复。
代码结构清楚:通过工厂模式管理对象创建逻辑,使代码结构更加清楚和规范。
便于扩展和维护:由于工厂模式使用接口和多态特性,当必要新增类型时,不影响原有代码。
缺点:
复杂性增加:简单场景下使用工厂模式多此一举,反而增加了代码复杂度。
不适用于简单对象:如果对象创建很简单,工厂模式会引入不必要的抽象,低落代码可读性。
难以追踪:工厂方法隐藏了具体实现,可能让代码调用链变长,增加调试难度。
类爆炸题目:如果产品种类繁多,则每种产品都必要一个对应的工厂类,导致类的数量大幅增加。
实现:
简单工厂模式:适用对象较少创建、逻辑简单的场景。
package main
import (
"fmt"
)
// 通知接口
type Notifier interface {
Notify(message string)
}
// 邮件通知
type EmailNotifier struct{}
func (e *EmailNotifier) Notify(message string) {
fmt.Println("Email Notification:", message)
}
// 短信通知
type SMSNotifier struct{}
func (s *SMSNotifier) Notify(message string) {
fmt.Println("SMS Notification:", message)
}
// 工厂函数
func NewNotifier(notifyType string) Notifier {
switch notifyType {
case "email":
return &EmailNotifier{}
case "sms":
return &SMSNotifier{}
default:
return nil
}
}
func main() {
notifier := NewNotifier("email")
if notifier != nil {
notifier.Notify("Hello via Email!")
}
notifier = NewNotifier("sms")
if notifier != nil {
notifier.Notify("Hello via SMS!")
}
}
复制代码
三、策略模式(Strategy Pattern)
简介:
一种行为设计模式,旨在将一组算法封装到独立的类中,使它们可以相互更换,通过使用策略模式,算法的变化不会影响使用算法的上下文代码。策略模式在Go语言中尤为常见,因为接口和结构体组合的特性使得实现即灵活又高效。
使用场景:
算法族:有多个可更换算法的场景,比方加密、排序。
业务规则变化频繁:比方支付策略、折扣策略。
制止使用条件语句:如大量if-else或switch判断的地方。
优点:
灵活性:可以在运行时动态更改策略。
代码复用:差别算法独立封装,制止了条件语句的堆叠。
依照开闭原则:添加新策略时无需修改上下文类。
缺点:
复杂性:每个策略必要创建一个类或结构体,增加代码量。
策略暴漏:客户端必要知道有哪些策略才能进行选择。
实现:
package main
import "fmt"
// 策略接口
type Strategy interface {
Execute(a, b int) int
}
// 加法策略
type AddStrategy struct{}
func (s AddStrategy) Execute(a, b int) int {
return a + b
}
// 乘法策略
type MultiplyStrategy struct{}
func (s MultiplyStrategy) Execute(a, b int) int {
return a * b
}
// 上下文结构体
type Context struct {
strategy Strategy
}
// 设置策略
func (c *Context) SetStrategy(strategy Strategy) {
c.strategy = strategy
}
// 执行策略
func (c *Context) ExecuteStrategy(a, b int) int {
return c.strategy.Execute(a, b)
}
func main() {
context := Context{}
// 使用加法策略
context.SetStrategy(AddStrategy{})
result := context.ExecuteStrategy(5, 3)
fmt.Println("加法策略结果:", result) // 输出:8
// 使用乘法策略
context.SetStrategy(MultiplyStrategy{})
result = context.ExecuteStrategy(5, 3)
fmt.Println("乘法策略结果:", result) // 输出:15
}
复制代码
四、观察者模式(Observer Pattern)
简介:
一种行为设计模式,允许对象在其状态发生更改时关照其他依赖对象。它定义了一种一对多的依赖关系,一个对象(主题/被观察者)状态变化时,全部依赖者(观察者)都会收到关照并自动更新。
使用场景:
事件驱动系统:如GUI事件监听、消息广播系统。
订阅-发布系统:如新闻发布、股票行情更新。
监控系统:如服务康健状态监控和报警。
优点:
解耦:主题和观察者之间的耦合性低,便于独立扩展。
灵活性:可以在运行时动态添加和删除观察者。
实时更新:状态变化时自动关照观察者,符合实时性需求。
缺点:
关照滞后:当观察者数量较多时,关照操纵可能会有一定耽误。
内存泄露风险:如果没有准确管理观察者的注销,可能会导致内存泄漏。
调试难度较大:链式调用可能增加排查时题目的复杂性。
实现:
package main
import "fmt"
// 观察者接口
type Observer interface {
Update(message string)
}
// 主题接口
type Subject interface {
Register(observer Observer)
Unregister(observer Observer)
NotifyAll(message string)
}
// 具体主题
type NewsPublisher struct {
observers []Observer
}
// 注册观察者
func (n *NewsPublisher) Register(observer Observer) {
n.observers = append(n.observers, observer)
}
// 注销观察者
func (n *NewsPublisher) Unregister(observer Observer) {
for i, obs := range n.observers {
if obs == observer {
n.observers = append(n.observers[:i], n.observers[i+1:]...)
break
}
}
}
// 通知所有观察者
func (n *NewsPublisher) NotifyAll(message string) {
for _, observer := range n.observers {
observer.Update(message)
}
}
// 具体观察者
type NewsSubscriber struct {
name string
}
// 接收更新通知
func (n *NewsSubscriber) Update(message string) {
fmt.Printf("[%s] 收到新闻更新:%s\n", n.name, message)
}
// 创建新的观察者
func NewSubscriber(name string) *NewsSubscriber {
return &NewsSubscriber{name: name}
}
func main() {
// 创建新闻发布者(主题) 隐式使用
// var publisher Subject = &NewsPublisher{}
publisher := &NewsPublisher{}
// 创建观察者(订阅者)
sub1 := NewSubscriber("Alice")
sub2 := NewSubscriber("Bob")
sub3 := NewSubscriber("Charlie")
// 注册观察者
publisher.Register(sub1)
publisher.Register(sub2)
publisher.Register(sub3)
// 发布新闻更新
publisher.NotifyAll("Go 1.21 发布了!")
// 注销一个观察者
publisher.Unregister(sub2)
// 再次发布新闻
publisher.NotifyAll("Go 1.22 即将发布!")
}
复制代码
五、署理模式(Proxy Pattern)
简介:
一种结构型设计模式,它通过一个署理对象来控制对目的对象的访问。署理对象可以在客户端和真实对象之间进行一些操纵,好比权限控制、懒加载、日记记录、缓存等,特别适合加强现有类的功能而无需修改原有代码。
使用场景:
远程署理:使用署理来控制对远程服务的访问。
虚拟署理:耽误初始化较为复杂的对象。
安全署理:查抄权限,只有合法用户才能访问。
只能引用署理:自动进行医用计数和资源管理。
优点:
职责分离:署理对象负责处置惩罚非核心业务(如日记记录),核心业务由真实对象完成。
灵活性强:可以动态地将额外操纵附加到真实对象上。
曾强功能:在不修改原始类的环境下增加新功能。
缺点:
开销增加:由于增加了署理对象,性能有一定损耗。
代码复杂:增加了代码复杂性和维护难度。
实现:
package main
import (
"fmt"
"time"
)
// 抽象接口(Subject)
type BankAccount interface {
Deposit(amount float64)
Withdraw(amount float64)
GetBalance() float64
}
// 实际对象(RealSubject):银行账户
type RealBankAccount struct {
balance float64
}
func (r *RealBankAccount) Deposit(amount float64) {
r.balance += amount
fmt.Printf("存入:%.2f 元,当前余额:%.2f 元\n", amount, r.balance)
}
func (r *RealBankAccount) Withdraw(amount float64) {
if amount > r.balance {
fmt.Println("余额不足,取款失败!")
return
}
r.balance -= amount
fmt.Printf("取出:%.2f 元,当前余额:%.2f 元\n", amount, r.balance)
}
func (r *RealBankAccount) GetBalance() float64 {
return r.balance
}
// 代理对象(Proxy):日志代理
type LoggingProxy struct {
realAccount BankAccount
}
func NewLoggingProxy(realAccount BankAccount) *LoggingProxy {
return &LoggingProxy{realAccount: realAccount}
}
func (p *LoggingProxy) Deposit(amount float64) {
fmt.Printf("[%s] 正在进行存款操作...\n", time.Now().Format("2006-01-02 15:04:05"))
p.realAccount.Deposit(amount)
}
func (p *LoggingProxy) Withdraw(amount float64) {
fmt.Printf("[%s] 正在进行取款操作...\n", time.Now().Format("2006-01-02 15:04:05"))
p.realAccount.Withdraw(amount)
}
func (p *LoggingProxy) GetBalance() float64 {
balance := p.realAccount.GetBalance()
fmt.Printf("[%s] 查询余额:%.2f 元\n", time.Now().Format("2006-01-02 15:04:05"), balance)
return balance
}
// 客户端代码
func main() {
// 创建实际银行账户
realAccount := &RealBankAccount{}
// 使用代理来包装实际账户
proxy := NewLoggingProxy(realAccount)
// 通过代理进行操作
proxy.Deposit(1000)
proxy.Withdraw(300)
proxy.GetBalance()
}
复制代码
六、装饰器模式
简介:
一种结构型设计模式,允许在不修改对象结构的环境下动态地为对象添加新功能。
使用场景:
功能加强:为对象动态添加功能。
替代子类继续:通过组合而非继续来扩展功能。
职责划分:使每个装饰器负责特定功能,符合单一职责原则。
装饰器模式的核心思想:
组件接口(Component):定义一个可以被装饰的对象接口。
具体组件(Concrete Component):实现底子功能。
装饰器接口(Decorator):持有组件接口的引用,且具有相同的方法。
具体装饰器(Concrete Decorator):扩展组件的功能。
优点:
灵活性高:可以通过多个装饰器动态组合新功能。
符合开闭原则:可以随时添加新装饰器而不影响原有代码。
职责单一:每个装饰器只负责一个特定功能。
缺点:
装饰链过长:可能导致结构复杂,难以维护。
性能开销:多层嵌套会带来性能损耗。
实现:
package main
import (
"fmt"
)
// Component 接口:咖啡饮品
type Beverage interface {
GetDescription() string
Cost() float64
}
// 具体组件:基础咖啡
type Espresso struct{}
func (e *Espresso) GetDescription() string {
return "Espresso"
}
func (e *Espresso) Cost() float64 {
return 15.0
}
// 装饰器基类:实现 Beverage 接口
type CondimentDecorator struct {
beverage Beverage
}
func (c *CondimentDecorator) GetDescription() string {
return c.beverage.GetDescription()
}
func (c *CondimentDecorator) Cost() float64 {
return c.beverage.Cost()
}
// 具体装饰器:牛奶
type Milk struct {
CondimentDecorator
}
func NewMilk(beverage Beverage) *Milk {
return &Milk{CondimentDecorator{beverage}}
}
func (m *Milk) GetDescription() string {
return m.beverage.GetDescription() + ", Milk"
}
func (m *Milk) Cost() float64 {
return m.beverage.Cost() + 3.5
}
// 具体装饰器:糖
type Sugar struct {
CondimentDecorator
}
func NewSugar(beverage Beverage) *Sugar {
return &Sugar{CondimentDecorator{beverage}}
}
func (s *Sugar) GetDescription() string {
return s.beverage.GetDescription() + ", Sugar"
}
func (s *Sugar) Cost() float64 {
return s.beverage.Cost() + 1.0
}
// 客户端代码
func main() {
// 创建基础咖啡
var beverage Beverage = &Espresso{}
fmt.Printf("饮品:%s,价格:%.2f 元\n", beverage.GetDescription(), beverage.Cost())
// 加牛奶
beverage = NewMilk(beverage)
fmt.Printf("饮品:%s,价格:%.2f 元\n", beverage.GetDescription(), beverage.Cost())
// 再加糖
beverage = NewSugar(beverage)
fmt.Printf("饮品:%s,价格:%.2f 元\n", beverage.GetDescription(), beverage.Cost())
}
复制代码
装饰器模式和署理模式对比
特性装饰器模式署理模式主要目的动态扩展功能控制对对象的访问结构特点组合多个装饰器形成链署理对象持有现实对象的引用典型应用场景日记、性能监控、权限校验、加强对象功能远程署理、虚拟署理、安全署理、缓存署理
七、适配器模式(Adapter Pattern)
简介:
一种结构型设计模式,它通过两个不兼容的接口提供一个适配器,使得它们可以或许一起工作。适配器模式可以将一个接口转换为客户端盼望的另一个接口,目的时让不兼容的接口能过够相助。
使用场景:
多重接口适配(同一接口标准):在一个系统中,你可能必要使用多个不兼容的接口,而这些接口都执行雷同的操纵。适配器模式可以帮助你同一这些接口,使得系统中其他部分可以通过相同的接口与它们交互。
兼容性题目(与现有系统兼容):如果你正在集成一个第三方库,大概使用一个遗留系统,而该系统的接口与现有系统不兼容,适配器模式可以帮助你转换这些不兼容的接口,使得它们可以或许顺遂协作。
第三方库接口的适配(外部API整合):当使用第三方库时,这些库通常提供差别的接口,而你希望用一个同一的接口访问这些库。适配器模式可以将第三方库的接口适配为你项目中必要的标准接口。
接口升级或变化:当你必要对现有接口进行修改,但又不希望影响到客户端的代码时,适配器模式可以帮助你维护原接口的兼容性,同时在背后对接口进行改造或升级。
差别硬件或设备的适配:当你在开辟跨平台应用大概硬件交互时,可能必要适配差别硬件或设备的接口。适配器模式可以帮助将差别硬件提供的接口适配到你应用必要的标准接口上。
系统迁移或重构:在系统重构的过程中,可能会涉及到接口的更改,而你又希望让现有代码与新代码兼容。适配器模式可以或许在过渡期间无缝衔接新旧系统的接口。
替代继续的场景(类适配):如果一个类不适用于继续的方式,大概你不想改变原有类的结构,可以使用适配器模式代替继续来扩展功能。
API或数据协议的适配:差别的系统或组件之间可能使用差别的数据格式或协议,适配器模式可以或许在这些差别的格式之间提供一个桥梁。
适配器模式的核心思想
目的接口(Target):客户端盼望使用的接口。
源接口(Adaptee):必要适配的现有接口,它的方法不能直接与客户端使用的接口兼容。
适配器(Adapter):将源接口转换为目的接口,使得客户端可以通过目的接口使用源接口的功能。
优点:
兼容不兼容的接口:使得本来不兼容的接口通过适配器可以或许协同工作。
符合开闭原则:通过适配器可以在不修改原有类的环境下,改变接口的使用方式。
解耦:客户端不必要知道适配器的实现,只必要依赖目的接口。
缺点:
增加代码复杂性:适配器模式会引入额外的类和对象,可能增加代码的复杂性。
性能开销:在适配器模式中,通常有额外的间接调用,可能导致稍微的性能损失。
实现:
package main
import "fmt"
// 目标接口(Target):要求的电源接口
type PowerOutlet interface {
SupplyPower() string
}
// 源接口(Adaptee):我们现有的电源接口
type TwoPinSocket struct{}
func (s *TwoPinSocket) ProvidePower() string {
return "提供 220V 电流"
}
// 适配器(Adapter):将现有电源接口转换为目标接口
type Adapter struct {
socket *TwoPinSocket
}
// 适配器的方法:使其实现目标接口
func (a *Adapter) SupplyPower() string {
return a.socket.ProvidePower()
}
// 客户端代码
func main() {
// 使用现有的 2 针电源插座(不符合目标接口)
twoPinSocket := &TwoPinSocket{}
// 通过适配器将其转换为目标接口
adapter := &Adapter{socket: twoPinSocket}
// 客户端通过目标接口使用适配后的电源
fmt.Println("设备电源:", adapter.SupplyPower())
}
复制代码
适配器模式与署理模式的对比
特性适配器模式署理模式主要目的使接口兼容并进行转换,适配差别接口的类控制对目的对象的访问,通常是耽误或虚拟化操纵。结构特点客户端和目的接口之间通过适配器进行转换署理对象持有现实对象的引用,进行控制访问典型应用场景使得不兼容的类可以或许协作,转换接口控制对现实对象的访问(如耽误加载、远程调用等)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/)
Powered by Discuz! X3.4