Go常用的设计模式常见的设计模式如单例模式、工厂模式、策略模式、观察者模式、代理模式、装饰器模式和适配器模式都可以在 Go 中实现适用于不同的开发需求。这些设计模式不仅能帮助你编写结构清晰、可维护的代码还能让你更好地应对复杂的编程问题。一、单例模式Singleton简介 Go 的单例模式Singleton Pattern适用于某些需要确保一个类或结构体在整个应用程序中只有一个实例的场景。通常情况下单例模式用于全局共享资源、缓存、日志管理、数据库连接等场景避免了不必要的对象创建和资源浪费。使用场景配置管理日志管理数据连接池缓存管理线程安全的全局状态管理系统资源管理优点线程安全使用sync.Once可以确保在并发环境下单例对象只被创建一次避免竞态条件和数据竞争。延迟初始化懒汉式只有在首次调用时才进行实例化节省内存和启动时间。全局唯一性单例模式可以确保在整个程序生命周期中只有一个实例适合需要共享资源的场景如配置管理、日志记录。节省资源由于实例只创建一次避免频繁创建和销毁带来的性能瓶颈。方便访问单例模式提供一个全局访问点通过调用同一个函数即可获取实例方便在整个程序中使用。缺点全局状态导致代码难以测试单例模式带来的全局状态使得单元测试难以编写尤其是需要模拟或替换单例时。难以扩展单例模式将创建逻辑硬编码唉单例类中如果需要更改创建逻辑往往需要修改核心代码违反开闭原则。隐藏依赖性使用单例时其他模块可能隐式依赖于单例对象在修改单例时容易引发不可预期的问题。不利于并行测试由于单例模式在程序中只有一个实例多个测试用例无法并发运行可能产生数据污染。潜在内存泄露如果单例中持有大量资源且没有及时释放可能导致内存泄漏。实现packagemainimport(fmtsync)typeSingletonstruct{// 可能包含一些属性}varinstance*Singletonvaronce sync.OncefuncGetInstance()*Singleton{once.Do(func(){instanceSingleton{}})returninstance}funcmain(){s1:GetInstance()s2:GetInstance()fmt.Println(s1s2)// true}二、工厂模式Factory简介 工厂模式用于创建对象的实例但不暴露具体的创建逻辑它通过接口来解耦对象的创建和使用。使用场景构建复杂对象如果对象创建过程复杂且有多种变体使用工厂可以贱货创建逻辑。接口实例化当代码中涉及多个实现相同接口的结构体时使用工厂可以屏蔽具体实现方便切换。减少重复代码如果对象创建涉及大量重复逻辑工厂方法可以封装这些逻辑。依赖注入DI和解耦使用工厂可以避免代码中直接依赖具体实现类提升代码灵活性。面向接口编程在某昔日场景下需要根据配置或运行时条件来创建不同的实例。优点解耦性高工厂方法将对象创建与对象使用分离调用者无需关心对象的具体创建过程。符合开闭原则增加新产品时只需增加新的工厂方法而无需修改原有代码减少了对现有代码的侵入。增强代码复用性工厂方法可以复用已有的创建逻辑避免代码重复。代码结构清晰通过工厂模式管理对象创建逻辑使代码结构更加清晰和规范。便于扩展和维护由于工厂模式使用接口和多态特性当需要新增类型时不影响原有代码。缺点复杂性增加简单场景下使用工厂模式多此一举反而增加了代码复杂度。不适用于简单对象如果对象创建很简单工厂模式会引入不必要的抽象降低代码可读性。难以追踪工厂方法隐藏了具体实现可能让代码调用链变长增加调试难度。类爆炸问题如果产品种类繁多则每种产品都需要一个对应的工厂类导致类的数量大幅增加。实现简单工厂模式适用对象较少创建、逻辑简单的场景。packagemainimport(fmt)// 通知接口typeNotifierinterface{Notify(messagestring)}// 邮件通知typeEmailNotifierstruct{}func(e*EmailNotifier)Notify(messagestring){fmt.Println(Email Notification:,message)}// 短信通知typeSMSNotifierstruct{}func(s*SMSNotifier)Notify(messagestring){fmt.Println(SMS Notification:,message)}// 工厂函数funcNewNotifier(notifyTypestring)Notifier{switchnotifyType{caseemail:returnEmailNotifier{}casesms:returnSMSNotifier{}default:returnnil}}funcmain(){notifier:NewNotifier(email)ifnotifier!nil{notifier.Notify(Hello via Email!)}notifierNewNotifier(sms)ifnotifier!nil{notifier.Notify(Hello via SMS!)}}三、策略模式Strategy Pattern简介 一种行为设计模式旨在将一组算法封装到独立的类中使它们可以互相替换通过使用策略模式算法的变化不会影响使用算法的上下文代码。策略模式在Go语言中尤为常见因为接口和结构体组合的特性使得实现即灵活又高效。使用场景算法族有多个可替换算法的场景例如加密、排序。业务规则变化频繁例如支付策略、折扣策略。避免使用条件语句如大量if-else或switch判断的地方。优点灵活性可以在运行时动态更改策略。代码复用不同算法独立封装避免了条件语句的堆叠。遵循开闭原则添加新策略时无需修改上下文类。缺点复杂性每个策略需要创建一个类或结构体增加代码量。策略暴漏客户端需要知道有哪些策略才能进行选择。实现packagemainimportfmt// 策略接口typeStrategyinterface{Execute(a,bint)int}// 加法策略typeAddStrategystruct{}func(s AddStrategy)Execute(a,bint)int{returnab}// 乘法策略typeMultiplyStrategystruct{}func(s MultiplyStrategy)Execute(a,bint)int{returna*b}// 上下文结构体typeContextstruct{strategy Strategy}// 设置策略func(c*Context)SetStrategy(strategy Strategy){c.strategystrategy}// 执行策略func(c*Context)ExecuteStrategy(a,bint)int{returnc.strategy.Execute(a,b)}funcmain(){context:Context{}// 使用加法策略context.SetStrategy(AddStrategy{})result:context.ExecuteStrategy(5,3)fmt.Println(加法策略结果:,result)// 输出8// 使用乘法策略context.SetStrategy(MultiplyStrategy{})resultcontext.ExecuteStrategy(5,3)fmt.Println(乘法策略结果:,result)// 输出15}四、观察者模式Observer Pattern简介 一种行为设计模式允许对象在其状态发生更改时通知其他依赖对象。它定义了一种一对多的依赖关系一个对象主题/被观察者状态变化时所有依赖者观察者都会收到通知并自动更新。使用场景事件驱动系统如GUI事件监听、消息广播系统。订阅-发布系统如新闻发布、股票行情更新。监控系统如服务健康状态监控和报警。优点解耦主题和观察者之间的耦合性低便于独立扩展。灵活性可以在运行时动态添加和删除观察者。实时更新状态变化时自动通知观察者符合实时性需求。缺点通知滞后当观察者数量较多时通知操作可能会有一定延迟。内存泄露风险如果没有正确管理观察者的注销可能会导致内存泄漏。调试难度较大链式调用可能增加排查时问题的复杂性。实现packagemainimportfmt// 观察者接口typeObserverinterface{Update(messagestring)}// 主题接口typeSubjectinterface{Register(observer Observer)Unregister(observer Observer)NotifyAll(messagestring)}// 具体主题typeNewsPublisherstruct{observers[]Observer}// 注册观察者func(n*NewsPublisher)Register(observer Observer){n.observersappend(n.observers,observer)}// 注销观察者func(n*NewsPublisher)Unregister(observer Observer){fori,obs:rangen.observers{ifobsobserver{n.observersappend(n.observers[:i],n.observers[i1:]...)break}}}// 通知所有观察者func(n*NewsPublisher)NotifyAll(messagestring){for_,observer:rangen.observers{observer.Update(message)}}// 具体观察者typeNewsSubscriberstruct{namestring}// 接收更新通知func(n*NewsSubscriber)Update(messagestring){fmt.Printf([%s] 收到新闻更新%s\n,n.name,message)}// 创建新的观察者funcNewSubscriber(namestring)*NewsSubscriber{returnNewsSubscriber{name:name}}funcmain(){// 创建新闻发布者主题 隐式使用// 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简介 一种结构型设计模式它通过一个代理对象来控制对目标对象的访问。代理对象可以在客户端和真实对象之间进行一些操作比如权限控制、懒加载、日志记录、缓存等特别适合增强现有类的功能而无需修改原有代码。使用场景远程代理使用代理来控制对远程服务的访问。虚拟代理延迟初始化较为复杂的对象。安全代理检查权限只有合法用户才能访问。只能引用代理自动进行医用计数和资源管理。优点职责分离代理对象负责处理非核心业务如日志记录核心业务由真实对象完成。灵活性强可以动态地将额外操作附加到真实对象上。曾强功能在不修改原始类的情况下增加新功能。缺点开销增加由于增加了代理对象性能有一定损耗。代码复杂增加了代码复杂性和维护难度。实现packagemainimport(fmttime)// 抽象接口SubjecttypeBankAccountinterface{Deposit(amountfloat64)Withdraw(amountfloat64)GetBalance()float64}// 实际对象RealSubject银行账户typeRealBankAccountstruct{balancefloat64}func(r*RealBankAccount)Deposit(amountfloat64){r.balanceamount fmt.Printf(存入%.2f 元当前余额%.2f 元\n,amount,r.balance)}func(r*RealBankAccount)Withdraw(amountfloat64){ifamountr.balance{fmt.Println(余额不足取款失败)return}r.balance-amount fmt.Printf(取出%.2f 元当前余额%.2f 元\n,amount,r.balance)}func(r*RealBankAccount)GetBalance()float64{returnr.balance}// 代理对象Proxy日志代理typeLoggingProxystruct{realAccount BankAccount}funcNewLoggingProxy(realAccount BankAccount)*LoggingProxy{returnLoggingProxy{realAccount:realAccount}}func(p*LoggingProxy)Deposit(amountfloat64){fmt.Printf([%s] 正在进行存款操作...\n,time.Now().Format(2006-01-02 15:04:05))p.realAccount.Deposit(amount)}func(p*LoggingProxy)Withdraw(amountfloat64){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)returnbalance}// 客户端代码funcmain(){// 创建实际银行账户realAccount:RealBankAccount{}// 使用代理来包装实际账户proxy:NewLoggingProxy(realAccount)// 通过代理进行操作proxy.Deposit(1000)proxy.Withdraw(300)proxy.GetBalance()}六、装饰器模式简介 一种结构型设计模式允许在不修改对象结构的情况下动态地为对象添加新功能。使用场景功能增强为对象动态添加功能。替代子类继承通过组合而非继承来扩展功能。职责划分使每个装饰器负责特定功能符合单一职责原则。装饰器模式的核心思想组件接口Component定义一个可以被装饰的对象接口。具体组件Concrete Component实现基础功能。装饰器接口Decorator持有组件接口的引用且具有相同的方法。具体装饰器Concrete Decorator扩展组件的功能。优点灵活性高可以通过多个装饰器动态组合新功能。符合开闭原则可以随时添加新装饰器而不影响原有代码。职责单一每个装饰器只负责一个特定功能。缺点装饰链过长可能导致结构复杂难以维护。性能开销多层嵌套会带来性能损耗。实现packagemainimport(fmt)// Component 接口咖啡饮品typeBeverageinterface{GetDescription()stringCost()float64}// 具体组件基础咖啡typeEspressostruct{}func(e*Espresso)GetDescription()string{returnEspresso}func(e*Espresso)Cost()float64{return15.0}// 装饰器基类实现 Beverage 接口typeCondimentDecoratorstruct{beverage Beverage}func(c*CondimentDecorator)GetDescription()string{returnc.beverage.GetDescription()}func(c*CondimentDecorator)Cost()float64{returnc.beverage.Cost()}// 具体装饰器牛奶typeMilkstruct{CondimentDecorator}funcNewMilk(beverage Beverage)*Milk{returnMilk{CondimentDecorator{beverage}}}func(m*Milk)GetDescription()string{returnm.beverage.GetDescription(), Milk}func(m*Milk)Cost()float64{returnm.beverage.Cost()3.5}// 具体装饰器糖typeSugarstruct{CondimentDecorator}funcNewSugar(beverage Beverage)*Sugar{returnSugar{CondimentDecorator{beverage}}}func(s*Sugar)GetDescription()string{returns.beverage.GetDescription(), Sugar}func(s*Sugar)Cost()float64{returns.beverage.Cost()1.0}// 客户端代码funcmain(){// 创建基础咖啡varbeverage BeverageEspresso{}fmt.Printf(饮品%s价格%.2f 元\n,beverage.GetDescription(),beverage.Cost())// 加牛奶beverageNewMilk(beverage)fmt.Printf(饮品%s价格%.2f 元\n,beverage.GetDescription(),beverage.Cost())// 再加糖beverageNewSugar(beverage)fmt.Printf(饮品%s价格%.2f 元\n,beverage.GetDescription(),beverage.Cost())}装饰器模式和代理模式对比特性装饰器模式代理模式主要目的动态扩展功能控制对对象的访问结构特点组合多个装饰器形成链代理对象持有实际对象的引用典型应用场景日志、性能监控、权限校验、增强对象功能远程代理、虚拟代理、安全代理、缓存代理七、适配器模式Adapter Pattern简介 一种结构型设计模式它通过两个不兼容的接口提供一个适配器使得它们能够一起工作。适配器模式可以将一个接口转换为客户端期望的另一个接口目的时让不兼容的接口能过够合作。使用场景多重接口适配统一接口标准在一个系统中你可能需要使用多个不兼容的接口而这些接口都执行类似的操作。适配器模式可以帮助你统一这些接口使得系统中其他部分可以通过相同的接口与它们交互。兼容性问题与现有系统兼容如果你正在集成一个第三方库或者使用一个遗留系统而该系统的接口与现有系统不兼容适配器模式可以帮助你转换这些不兼容的接口使得它们能够顺利协作。第三方库接口的适配外部API整合当使用第三方库时这些库通常提供不同的接口而你希望用一个统一的接口访问这些库。适配器模式可以将第三方库的接口适配为你项目中需要的标准接口。接口升级或变化当你需要对现有接口进行修改但又不希望影响到客户端的代码时适配器模式可以帮助你维护原接口的兼容性同时在背后对接口进行改造或升级。不同硬件或设备的适配当你在开发跨平台应用或者硬件交互时可能需要适配不同硬件或设备的接口。适配器模式可以帮助将不同硬件提供的接口适配到你应用需要的标准接口上。系统迁移或重构在系统重构的过程中可能会涉及到接口的更改而你又希望让现有代码与新代码兼容。适配器模式能够在过渡期间无缝衔接新旧系统的接口。替代继承的场景类适配如果一个类不适用于继承的方式或者你不想改变原有类的结构可以使用适配器模式代替继承来扩展功能。API或数据协议的适配不同的系统或组件之间可能使用不同的数据格式或协议适配器模式能够在这些不同的格式之间提供一个桥梁。适配器模式的核心思想目标接口Target客户端期望使用的接口。源接口Adaptee需要适配的现有接口它的方法不能直接与客户端使用的接口兼容。适配器Adapter将源接口转换为目标接口使得客户端可以通过目标接口使用源接口的功能。优点兼容不兼容的接口使得本来不兼容的接口通过适配器能够协同工作。符合开闭原则通过适配器可以在不修改原有类的情况下改变接口的使用方式。解耦客户端不需要知道适配器的实现只需要依赖目标接口。缺点增加代码复杂性适配器模式会引入额外的类和对象可能增加代码的复杂性。性能开销在适配器模式中通常有额外的间接调用可能导致轻微的性能损失。实现packagemainimportfmt// 目标接口Target要求的电源接口typePowerOutletinterface{SupplyPower()string}// 源接口Adaptee我们现有的电源接口typeTwoPinSocketstruct{}func(s*TwoPinSocket)ProvidePower()string{return提供 220V 电流}// 适配器Adapter将现有电源接口转换为目标接口typeAdapterstruct{socket*TwoPinSocket}// 适配器的方法使其实现目标接口func(a*Adapter)SupplyPower()string{returna.socket.ProvidePower()}// 客户端代码funcmain(){// 使用现有的 2 针电源插座不符合目标接口twoPinSocket:TwoPinSocket{}// 通过适配器将其转换为目标接口adapter:Adapter{socket:twoPinSocket}// 客户端通过目标接口使用适配后的电源fmt.Println(设备电源,adapter.SupplyPower())}适配器模式与代理模式的对比特性适配器模式代理模式主要目的使接口兼容并进行转换适配不同接口的类控制对目标对象的访问通常是延迟或虚拟化操作。结构特点客户端和目标接口之间通过适配器进行转换代理对象持有实际对象的引用进行控制访问典型应用场景使得不兼容的类能够协作转换接口控制对实际对象的访问如延迟加载、远程调用等