Golang 策略设计模式

本文主要介绍了在 Golang 中实现策略设计模式(Strategy Design Pattern)的方法和优势。策略设计模式是一种用于处理多种相似算法或行为的设计模式,允许在运行时动态切换算法。原文: Strategy Design Pattern in Golang[1]

alt

假设有一个名为 PaymentStrategy 的接口,包含一个名为 Pay() 的方法,有两种名为 CreditCardStrategyDebitCardStrategy 的类型实现了该接口。我们希望根据不同情况,PaymentStrategy.Pay() 可以执行 CreditCardStrategy 类型或 DebitCardStrategy 类型的实现。如何才能做到这一点呢?

可以通过策略设计模式来实现这一目标,但首先我们需要知道什么是策略设计模式?


策略模式处理的情况是,有多种算法或行为(算法可能有不同的实现方式,但目的类似),这些算法或行为可以互换或动态使用。例如,可能有不同的排序算法,如冒泡排序、合并排序和快速排序,目的都是对一个集合进行排序。另一个例子是 Pay() 方法,对于 CreditCardStrategy 类型和 DebitCardStrategy 类型,Pay() 方法的实现可能不同,但两种方法的目的是一样的。

上面提到不同算法,每种算法都封装在自己的类或类型中,这个类或类型代表算法的独立策略,这样的类被称为策略类。策略类提供一组方法,用于定义策略的执行方式。例如,CreditCardStrategyDebitCardStrategy 这两种类型都实现了 Pay() 方法。因此,我们可以说每个 Pay() 方法都封装了一个类型,每个类型代表一个单独的策略。例如,CreditCardStrategy 类型表示使用信用卡付款,而 DebitCardStrategy 类型表示将使用借记卡付款。也就是说,这两种类型分别代表两种策略,这就是它们被称为策略类或策略类型的原因。

在策略设计模式中,我们可以在运行时动态切换或选择策略,这意味着可以在不修改代码的情况下改变对象使用的算法。策略的选择可以基于各种因素,如用户输入、配置设置或特定条件。例如,如果用户需要,可以使用 CreditCardStrategy,也可以使用 DebitCardStrategy。为此,我们不需要在编码中做任何修改,而是可以动态处理。

策略模式促进了使用算法的代码(客户端代码)与算法的实际实现(策略类)之间的松耦合。客户端代码通过通用接口或抽象与策略进行交互,而无需了解每个策略实现的具体细节。这使得代码库具有更高的灵活性、可维护性和可扩展性。例如,这里的通用接口是 PaymentStrategy,客户端代码只需要与 PaymentStrategy 方法通信,而不需要与策略通信。也就是说,不需要考虑 CreditCardStrategy 类型或 DebitCardStrategy,客户代码与它们没有任何关系。代码只与通用接口相关联,根据实际情况,通用接口将决定实施哪一个。

因此,从这里我们可以列出策略设计模式的要点:

  • 可互换算法
  • 将每种算法封装为单独的类
  • 策略在运行时可以互换
  • 避免算法实现与客户端代码紧耦合

举个例子:

package main

import "fmt"

type PaymentStrategy interface {
 Pay()
}

// Credit Card Strategy Type
type CreditCardStrategy struct{}

func (c *CreditCardStrategy) Pay() {
 fmt.Println("Paying using Credit Card")
}

// Debit Card Strategy Type
type DebitCardStrategy struct{}

func (d *DebitCardStrategy) Pay() {
 fmt.Println("Paying using Debit Card")
}

// Visa Card Strategy Type
type VisaCardStrategy struct{}

func (v *VisaCardStrategy) Pay() {
 fmt.Println("Paying using Visa Card")
}

// This type sets the strategy dynamically
type PaymentMethod struct {
 paymentStrategy PaymentStrategy
}

func (p *PaymentMethod) setPaymentMethodStrategy(strategy PaymentStrategy) {
 p.paymentStrategy = strategy
}

func (p *PaymentMethod) checkOut() {
 p.paymentStrategy.Pay()
}

func main() {
 paymentMethod := &PaymentMethod{}

 // Credit Card
 creditCardStrategy := &CreditCardStrategy{}
 paymentMethod.setPaymentMethodStrategy(creditCardStrategy)
 paymentMethod.checkOut()

 // Debit Card
 debitCardStrategy := &DebitCardStrategy{}
 paymentMethod.setPaymentMethodStrategy(debitCardStrategy)
 paymentMethod.checkOut()

 // Visa Card
 visaCardStrategy := &VisaCardStrategy{}
 paymentMethod.setPaymentMethodStrategy(visaCardStrategy)
 paymentMethod.checkOut()
}

示例中有三种不同的支付策略,分别是 CreditCardStrategyVisaCardStrategyDebitCardStrategy。每种策略的支付流程不同,但目的相同,都是支付。

PaymentMethod 类型处理策略的选择。代码只需与 PaymentMethod 联系,PaymentMethod 根据情况决定选择哪种策略。为此,我们不需要对 PaymentMethod 类型做任何修改。或者说,我们不需要在 PaymentMethod 类型中定义任何有关策略的内容。这就是策略设计模式的魅力所在。我们不需要提及策略,但可以使用它们。这就是所谓的松耦合。

让我们再举一个例子:

package main

import "fmt"

type Animal interface {
 bark()
}

type Dog struct{}

func (d *Dog) bark() {
 fmt.Println("Bark by Dog")
}

type Cat struct{}

func (c *Cat) bark() {
 fmt.Println("Bark by a Cat")
}

type Bark struct {
 animal Animal
}

func (b *Bark) setBarkType(barkType Animal) {
 b.animal = barkType
}

func (b *Bark) checkBark() {
 b.animal.bark()
}

func main() {
 animalBark := &Bark{}

 dogBark := &Dog{}
 animalBark.setBarkType(dogBark)
 animalBark.checkBark()

 catBark := &Cat{}
 animalBark.setBarkType(catBark)
 animalBark.checkBark()
}

最后举一个真实的例子。

我们以文本编辑器为例,文本编辑器需要支持不同的文本格式选项,如粗体、斜体和下划线。对于不同的文本格式选项,并不需要在文本编辑器类中实现这些格式选项,而是可以根据选择应用策略模式。

每个文本格式选项(粗体、斜体、下划线)都封装在单独的策略类中,每个策略类都实现了通用接口或抽象类,定义了应用格式化的行为。文本编辑器类有对当前格式化策略的引用,并用它对选定文本应用格式化。在运行时,用户可以在不同的格式化策略之间切换,文本编辑器会应用所选的格式化,而无需修改其核心代码。

通过在这种情况下使用策略模式,文本编辑器实现了灵活性,允许用户在运行时选择格式化策略,而无需将文本编辑器代码与特定格式化实现紧密耦合。这种关注点分离和策略的可互换性正是策略设计模式的精髓所在。

策略设计模式的优势

  • 改进代码组织:通过使用策略模式,可以将算法行为分离到不同的策略类或类型中,从而实现代码的简洁和可重用性。每个策略类专注于特定算法,使代码更易于理解和维护。
  • 增强灵活性和可维护性:以 PaymentStrategy 为例。假设我们引入了新的 PaymentStrategy,不需要对 PaymentMethod 类做任何修改,只需声明一个实现了 Pay() 方法的类型即可。这将增加灵活性。
  • 可重用性:我们可以在需要类似行为的不同对象或系统中重复使用策略。一旦定义了策略,就可以轻松将其插入多个上下文中,而无需重复代码。
  • 可读性:由于每个策略都有独立的策略类及其实现方式,代码变得可读、可辨。
  • 可测试性:代码变得可读,测试代码也就更容易。如果出现任何问题,也很容易修复错误,可以很容易找到错误,并知道如何处理。

策略设计模式的注意事项

就策略设计模式而言,必须牢记以下几点:

  • 类数量增加:如果策略数量增加,类的数量也会增加。虽然这可以改善代码组织,但也会增加整体设计的复杂性,代码库会变得更大。重要的是要达到平衡,避免创建过多的类。
  • 策略的架构:以适当层次或顺序定义策略非常重要。我们需要查找重复的类,不同的类可能有相同的实现,为此可以使用单个类。
  • 初始化以及策略选择:需要决定何时以及如何初始化策略,以及如何在运行时选择合适的策略,需要非常谨慎的处理这一点。这可以通过依赖注入、配置文件或基于特定条件或用户输入的动态选择来实现。

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

Strategy Design Pattern in Golang: https://blog.stackademic.com/strategy-design-pattern-in-golang-52024a97d417

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俞凡 DeepNoMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值