Skip to content

策略模式(Strategy Pattern)

意图

定义一系列算法,封装每个算法,并使它们可以互换。

策略模式可以让算法独立于使用它的客户端。

类图

  • Strategy接口定义了一个算法族,它们都具有behavior() 方法。
  • Context是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior()setStrategy(in Strategy) 方法可以动态地改变 strategy对象,也就是说能动态地改变 Context所使用的算法。

img

与状态模式的比较

状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context所组合的 State对象,而策略模式是通过 Context本身的决策来改变组合的 Strategy对象。所谓的状态转移,是指 Context在运行过程中由于一些条件发生改变而使得 State对象发生改变,注意必须要是在运行过程中。

状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context使用的算法。

实现

设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。

public interface QuackBehavior {
    void quack();
}
public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("quack!");
    }
}
public class Squeak implements QuackBehavior{
    @Override
    public void quack() {
        System.out.println("squeak!");
    }
}
public class Duck {
    private QuackBehavior quackBehavior;

    public void performQuack() {
        if (quackBehavior != null) {
            quackBehavior.quack();
        }
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}
public class Client {
    public static void main(String[] args) {
        Duck duck = new Duck();
        duck.setQuackBehavior(new Squeak());
        duck.performQuack();
        duck.setQuackBehavior(new Quack());
        duck.performQuack();
    }
}

squeak!
quack!

策略模式golang实现

定义一系列算法,让这些算法在运行时可以互换,使得分离算法,符合开闭原则。

strategy.go

package strategy

import "fmt"

type Payment struct {
    context  *PaymentContext
    strategy PaymentStrategy
}

type PaymentContext struct {
    Name, CardID string
    Money        int
}

func NewPayment(name, cardid string, money int, strategy PaymentStrategy) *Payment {
    return &Payment{
        context: &PaymentContext{
            Name:   name,
            CardID: cardid,
            Money:  money,
        },
        strategy: strategy,
    }
}

func (p *Payment) Pay() {
    p.strategy.Pay(p.context)
}

type PaymentStrategy interface {
    Pay(*PaymentContext)
}

type Cash struct{}

func (*Cash) Pay(ctx *PaymentContext) {
    fmt.Printf("Pay $%d to %s by cash", ctx.Money, ctx.Name)
}

type Bank struct{}

func (*Bank) Pay(ctx *PaymentContext) {
    fmt.Printf("Pay $%d to %s by bank account %s", ctx.Money, ctx.Name, ctx.CardID)

}

strategy_test.go

package strategy

func ExamplePayByCash() {
    payment := NewPayment("Ada", "", 123, &Cash{})
    payment.Pay()
    // Output:
    // Pay $123 to Ada by cash
}

func ExamplePayByBank() {
    payment := NewPayment("Bob", "0002", 888, &Bank{})
    payment.Pay()
    // Output:
    // Pay $888 to Bob by bank account 0002
}