跳转到内容

策略

25% developed
来自维基教科书,开放的书籍,面向开放的世界

状态 计算机科学设计模式
策略
模板方法

范围

对象

目的

行为

意图

定义一系列算法,封装每个算法,并使它们可以互换,以便客户端和算法能够独立地变化。

适用性

  • 当一个对象应该使用几种算法之一配置时,
  • 并且所有算法都可以封装,
  • 并且一个接口涵盖所有封装。

结构

后果

  • + 更大的灵活性,重用
  • + 可以动态地改变算法
  • - 策略创建和通信开销
  • - 策略接口不灵活

实现

  • 在策略与其上下文之间交换信息
  • 通过模板进行静态策略选择
  • 状态,可以激活多个状态,而策略只能激活一个算法。
  • 享元,提供一个共享对象,可以同时在多个上下文中使用,而策略则专注于一个上下文。
  • 装饰器,改变对象的皮肤,而策略改变对象的内在机制。
  • 组合,与策略结合使用以提高效率。

描述

假设您在一家开发策略游戏的公司工作。让我们假设您已经提出了以下类层次结构。所有角色都能够行走,并且还有一种方法可以在屏幕上渲染它们。超类负责walk()方法的实现,而每个子类都提供自己的display()实现,因为每个角色看起来都不一样。

一个新的需求出现了,角色也需要战斗。您可能会说,这很简单,只需在Character超类中添加一个fight()方法即可。但是,等等,Worker怎么办?他们不能战斗!

好吧,您可能会认为,您可以在Worker子类中简单地覆盖fight()方法,使其什么也不做。

 class Worker {
      ....
      void fight() {
         // do nothing
      }
      ....
 }

但如果将来需要一个Guard类,它可以战斗但不能行走?我们需要一个更简洁的解决方案。如何将战斗和行走行为从超类中提取到接口中?这样,只有应该行走的角色才会实现Walkable接口,而只有应该战斗的角色才会实现Fightable接口。

好吧,这是另一种说法,即重复代码。如果需要对战斗行为进行少量更改,您需要修改所有类。此时,我们应该制定三个设计原则,以遵循我们的应用程序开发。

  1. 识别应用程序中变化的部分,并将它们与保持不变的部分分离。将变化的部分“封装”起来,使其不会影响代码的其余部分。
  2. 面向接口编程,而不是面向实现编程。
  3. 优先选择组合而不是继承。

所以,让我们应用第一个设计原则。将战斗和行走行为提取到不同的类中。为了应用第二个设计原则,我们必须将这些行为提取到接口中。因此,我们创建一个新的WeaponBehavior接口来处理战斗行为,类似地,创建一个WalkBehavior接口来处理行走行为。

Character的行为存在于单独的类中,这些类实现特定的行为接口。这样,Character类就不需要知道其自身行为的任何实现细节。此外,我们不再依赖于实现,而是依赖于接口。其他类型的对象也可以使用这些行为,因为它们没有隐藏在我们的Character类中。我们可以在不修改任何现有行为或触碰我们的角色类的情况下添加新行为。所以现在,我们所要做的就是让我们的Character类将所有行为信息委托给这两个行为接口(因此,这里出现了第三个设计原则)。我们通过在Character中添加两个实例变量来实现这一点:weaponwalk

 public abstract class Character {
  private WeaponBehavior weapon;
  private WalkBehavior walk;

  public void fight() {
     weapon.useWeapon(); // delegation of fight behavior
  }

  public void setWeapon(WeaponBehavior w) {
    weapon = w;
  }
  ...
  abstract void display();
 }

每个角色对象将在运行时多态地设置这些变量,以引用它想要的特定行为类型。

 public class Knight extends Character {
  public Knight() {
    weapon = new SwordBehavior();
    walk = new GallopBehavior();
  }

  public void display() {
    ...
  }
 }

将这些行为视为算法族。因此,组合为您提供了很大的灵活性。它不仅允许您将一系列算法封装到它们自己的类集中,而且还允许您在运行时更改行为,只要您正在组合的对象实现了正确的行为接口。

示例

Clipboard

待办事项
找到一个例子。


成本

在实现此模式之前三思而后行。您必须确保您的需求是频繁更改算法。您必须清楚地预见未来,否则,此模式将比基本实现更昂贵。

创建

此模式的创建成本很高。

维护

此模式的维护成本可能很高。如果类的表示形式经常发生变化,您将需要进行大量重构。

删除

此模式也很难删除。

建议

  • 使用策略一词向其他开发人员表明使用该模式。

实现

在 ActionScript 3 中实现

ActionScript 3 中的策略模式

//invoked from application.initialize
private function init() : void
{
    var context:Context;

    context = new Context( new ConcreteStrategyA() );
    context.execute();

    context = new Context( new ConcreteStrategyB() );
    context.execute();

    context = new Context( new ConcreteStrategyC() );
    context.execute();
}

package org.wikipedia.patterns.strategy
{
    public interface IStrategy
    {
    function execute() : void ;
    }
}

package org.wikipedia.patterns.strategy
{
    public final class ConcreteStrategyA implements IStrategy
    {
    public function execute():void
    {
         trace( "ConcreteStrategyA.execute(); invoked" );
    }
    }
}

package org.wikipedia.patterns.strategy
{
    public final class ConcreteStrategyB implements IStrategy
    {
    public function execute():void
    {
         trace( "ConcreteStrategyB.execute(); invoked" );
    }
    }
}

package org.wikipedia.patterns.strategy
{
    public final class ConcreteStrategyC implements IStrategy
    {
    public function execute():void
    {
         trace( "ConcreteStrategyC.execute(); invoked" );
    }
    }
}

package org.wikipedia.patterns.strategy
{
   public class Context
   {
    private var strategy:IStrategy;
       
    public function Context(strategy:IStrategy)
    {
         this.strategy = strategy;
    }
       
    public function execute() : void
    {  
             strategy.execute();
    }
    }
}
在 C 中实现

C 中,可以使用结构体来定义类,并使用函数指针来设置策略。以下是 Python 示例的镜像,并使用 C99 特性

#include <stdio.h>

void print_sum(int n, int *array) {
    int total = 0;
    for (int i = 0; i < n; i++)
        total += array[i];
    printf("%d", total);
}

void print_array(int n, int *array) {
    for (int i = 0; i < n; i++)
        printf("%d ", array[i]);
}

typedef struct {
    void (*submit_func)(int n, int *array);  // function pointer
    char *label;                             // instance label
} Button;

int main(void) {
    // Create two instances with different strategies
    Button button1 = { print_sum, "Add 'em" };
    Button button2 = { print_array, "List 'em" };

    int n = 10;
    int numbers[n];
    for (int i = 0; i < n; i++)
        numbers[i] = i;

    button1.submit_func(n, numbers);
    button2.submit_func(n, numbers);
 
    return 0;
}
在 C++ 中实现

C++ 中的策略模式类似于 Java,但不需要动态分配对象。

#include <iostream>

class Strategy
{
public:
     virtual int execute (int a, int b) = 0; // execute() is a so-called pure virtual function
                                             // as a consequence, Strategy is a so-called abstract class
};
 
class ConcreteStrategyAdd:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyAdd's execute()\n";
        return a + b;
    }
};
 
class ConcreteStrategySubstract:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategySubstract's execute()\n";
        return a - b;
    }
};
 
class ConcreteStrategyMultiply:public Strategy
{
public:
    int execute(int a, int b)
    {
        std::cout << "Called ConcreteStrategyMultiply's execute()\n";
        return a * b;
    }
};

class Context
{
private:
    Strategy* pStrategy;
 
public:

    Context (Strategy& strategy)
        : pStrategy(&strategy)
    {
    }

    void SetStrategy(Strategy& strategy)
    {
        pStrategy = &strategy;
    }
 
    int executeStrategy(int a, int b)
    {
        return pStrategy->execute(a,b);
    }
};
 
int main()
{
    ConcreteStrategyAdd       concreteStrategyAdd;
    ConcreteStrategySubstract concreteStrategySubstract;
    ConcreteStrategyMultiply  concreteStrategyMultiply;

    Context context(concreteStrategyAdd);
    int resultA = context.executeStrategy(3,4);
 
    context.SetStrategy(concreteStrategySubstract);
    int resultB = context.executeStrategy(3,4);
 
    context.SetStrategy(concreteStrategyMultiply);
    int resultC = context.executeStrategy(3,4);
 
    std::cout << "resultA: " << resultA << "\tresultB: " << resultB << "\tresultC: " << resultC << "\n";
}
在 C# 中实现
public class StrategyPatternWiki
{
    public static void Main(String[] args)
    {
        // Prepare strategies
        var normalStrategy    = new NormalStrategy();
        var happyHourStrategy = new HappyHourStrategy();

        var firstCustomer = new CustomerBill(normalStrategy);

        // Normal billing
        firstCustomer.Add(1.0, 1);

        // Start Happy Hour
        firstCustomer.Strategy = happyHourStrategy;
        firstCustomer.Add(1.0, 2);

        // New Customer
        var secondCustomer = new CustomerBill(happyHourStrategy);
        secondCustomer.Add(0.8, 1);
        // The Customer pays
        firstCustomer.Print();

        // End Happy Hour
        secondCustomer.Strategy = normalStrategy;
        secondCustomer.Add(1.3, 2);
        secondCustomer.Add(2.5, 1);
        secondCustomer.Print();
    }
}

// CustomerBill as class name since it narrowly pertains to a customer's bill
class CustomerBill
{
    private IList<double> drinks;

    // Get/Set Strategy
    public IBillingStrategy Strategy { get; set; }

    public CustomerBill(IBillingStrategy strategy)
    {
        this.drinks = new List<double>();
        this.Strategy = strategy;
    }

    public void Add(double price, int quantity)
    {
        this.drinks.Add(this.Strategy.GetActPrice(price) * quantity);
    }

    // Payment of bill
    public void Print()
    {
        double sum = 0;
        foreach (var drinkCost in this.drinks)
        {
            sum += drinkCost;
        }
        Console.WriteLine($"Total due: {sum}.");
        this.drinks.Clear();
    }
}

interface IBillingStrategy
{
    double GetActPrice(double rawPrice);
}

// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
    public double GetActPrice(double rawPrice) => rawPrice;
}

// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{
    public double GetActPrice(double rawPrice) => rawPrice * 0.5;
}

另一个示例

C# 中的委托遵循策略模式,其中委托定义定义了策略接口,而委托实例表示具体的策略。.NET 3.5 定义了 Func<,> 委托,可以用于快速实现策略模式,如下面的示例所示。请注意定义委托实例的 3 种不同方法。

using System;
using System.Linq;
class Program
{
    static void Main(string[] args)
    {
        var context = new Context<int>();

        // Delegate
        Func<int, int, int> concreteStrategy1 = PerformLogicalBitwiseOr;

        // Anonymous Delegate
        Func<int, int, int> concreteStrategy2 = delegate(int op1, int op2) { return op1 & op2; };

        // Lambda Expressions
        Func<int, int, int> concreteStrategy3 = (op1, op2) => op1 >> op2;
        Func<int, int, int> concreteStrategy4 = (op1, op2) => op1 << op2;

        context.Strategy = concreteStrategy1;
        var result1 = context.Execute(8, 9);
        context.Strategy = concreteStrategy2;
        var result2 = context.Execute(8, 9);
        context.Strategy = concreteStrategy3;
        var result3 = context.Execute(8, 1);
        context.Strategy = concreteStrategy4;
        var result4 = context.Execute(8, 1);
    }

    static int PerformLogicalBitwiseOr(int op1, int op2)
    {
        return op1 | op2;
    }

    class Context<T>
    {
        public Func<T, T, T> Strategy { get; set; }

        public T Execute(T operand1, T operand2)
        {
            return this.Strategy != null
                ? this.Strategy(operand1, operand2)
                : default(T);
        }
    }
}

使用接口

using System;

namespace Wikipedia.Patterns.Strategy
{
  // The strategy we will implement will be
  // to advise on investments.
  interface IHasInvestmentStrategy
  {
    long CalculateInvestment();
  }
  // Here we have one way to go about it.
  class FollowTheMoon : IHasInvestmentStrategy
  {
    protected virtual int MoonPhase { get; set; }
    protected virtual int AstrologicalSign { get; set; }
    public FollowTheMoon(int moonPhase, int yourSign)
    {
      MoonPhase = moonPhase;
      AstrologicalSign = yourSign;
    }
    public long CalculateInvestment()
    {
      if (MoonPhase == AstrologicalSign)
        return 1000;
      else
        return 100 * (MoonPhase % DateTime.Today.Day);
    }
  }
  // And here we have another.
  // Note that each strategy may have its own dependencies.
  // The EverythingYouOwn strategy needs a bank account.
  class EverythingYouOwn : IHasInvestmentStrategy
  {
    protected virtual OtherLib.IBankAccessor Accounts { get; set; }
    public EverythingYouOwn(OtherLib.IBankAccessor accounts)
    {
      Accounts = accounts;
    }
    public long CalculateInvestment()
    {
      return Accounts.GetAccountBalancesTotal();
    }
  }
  // The InvestmentManager is where we want to be able to
  // change strategies. This is the Context.
  class InvestmentManager
  {
    public IHasInvestmentStrategy Strategy { get; set; }
    public InvestmentManager(IHasInvestmentStrategy strategy)
    {
      Strategy = strategy;
    }
    public void Report()
    {
      // Our investment is determined by the current strategy.
      var investment = Strategy.CalculateInvestment();
      Console.WriteLine("You should invest {0} dollars!",
        investment);
    }
  }
  class Program
  {
    static void Main()
    {
      // Define some of the strategies we will use.
      var strategyA = new FollowTheMoon( 8, 8 );
      var strategyB = new EverythingYouOwn(
        OtherLib.BankAccountManager.MyAccount);
      // Our investment manager
      var manager = new InvestmentManager(strategyA);
      manager.Report();
      // You should invest 1000 dollars!
      manager.Strategy = strategyB;
      manager.Report();
      // You should invest 13521500000000 dollars!
    }
  }
}
在 Common Lisp 中实现

Common Lisp 中的示例:使用策略类

(defclass context ()
  ((strategy :initarg :strategy :accessor strategy)))

(defmethod execute ((c context))
  (execute (slot-value c 'strategy)))

(defclass strategy-a () ())

(defmethod execute ((s strategy-a))
  (print "Doing the task the normal way"))

(defclass strategy-b () ())

(defmethod execute ((s strategy-b))
  (print "Doing the task alternatively"))

(execute (make-instance 'context
                        :strategy (make-instance 'strategy-a)))

在 Common Lisp 中使用一等函数

(defclass context ()
  ((strategy :initarg :strategy :accessor strategy)))

(defmethod execute ((c context))
  (funcall (slot-value c 'strategy)))

(let ((a (make-instance 'context
                        :strategy (lambda ()
                                    (print "Doing the task the normal way")))))
  (execute a))

(let ((b (make-instance 'context
                        :strategy (lambda ()
                                    (print "Doing the task alternatively")))))
  (execute b))
在 Falcon 中实现

类似于 Python 和 Scala,Falcon 支持一等函数。以下代码实现了 Python 示例中看到的基本功能。

  /*#
    @brief A very basic button widget
  */
  class Button( label, submit_func )
    label = label
    on_submit = submit_func
  end
 
  // Create two instances with different strategies ...
  // ... and different ways to express inline functions
  button1 = Button( "Add 'em",
      function(nums)
         n = 0
         for val in nums: n+= val
         return n
       end )
  button2 = Button( "Join 'em", { nums => " ".merge( [].comp(nums) ) } )
 
  // Test each button
  numbers = [1: 10]  
  printl(button1.on_submit(numbers))   // displays "45"
  printl(button2.on_submit(numbers))   // displays "1 2 3 4 5 6 7 8 9"
在 Fortran 中实现

Fortran 2003 添加了过程指针、抽象接口以及一等函数。以下是 Python 示例的镜像。

module m_strategy_pattern
implicit none

abstract interface
    !! A generic interface to a subroutine accepting array of integers
    subroutine generic_function(numbers)
        integer, dimension(:), intent(in) :: numbers
    end subroutine
end interface

type :: Button
    character(len=20) :: label
    procedure(generic_function), pointer, nopass :: on_submit
contains
    procedure :: init
end type Button

contains

    subroutine init(self, func, label)
        class(Button), intent(inout) :: self
        procedure(generic_function) :: func
        character(len=*) :: label
        self%on_submit => func      !! Procedure pointer
        self%label = label
    end subroutine init

    subroutine summation(array)
        integer, dimension(:), intent(in) :: array
        integer :: total
        total = sum(array)
        write(*,*) total
    end subroutine summation

    subroutine join(array)
        integer, dimension(:), intent(in) :: array
        write(*,*) array        !! Just write out the whole array
    end subroutine join

end module m_strategy_pattern

!! The following program demonstrates the usage of the module
program test_strategy
use m_strategy_pattern
implicit none

    type(Button) :: button1, button2
    integer :: i

    call button1%init(summation, "Add them")
    call button2%init(join, "Join them")

    call button1%on_submit([(i, i=1,10)])   !! Displays 55
    call button2%on_submit([(i, i=1,10)])   !! Prints out the array

end program test_strategy
在 Groovy 中实现

此 Groovy 示例是使用块的 Ruby 示例的基本端口。示例中使用 Groovy 的闭包支持来代替 Ruby 的块。

class Context {
  def strategy

  Context(strategy) {
    this.strategy = strategy
  }
 
  def execute() {
    strategy()
  }
}

def a = new Context({ println 'Style A' })
a.execute() // => Style A
def b = new Context({ println 'Style B' })
b.execute() // => Style B
def c = new Context({ println 'Style C' })
c.execute() // => Style C
在“经典”Java 中实现

Java 中的示例

/** The classes that implement a concrete strategy should implement this.
* The Context class uses this to call the concrete strategy. */
interface Strategy {
    int execute(int a, int b);
}
/** Implements the algorithm using the strategy interface */
class Add implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Add's execute()");
        return a + b;  // Do an addition with a and b
    }
}
class Subtract implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Subtract's execute()");
        return a - b;  // Do a subtraction with a and b
    }
}
class Multiply implements Strategy {
    public int execute(int a, int b) {
        System.out.println("Called Multiply's execute()");
        return a * b;   // Do a multiplication with a and b
    }    
}
/** Configured with a ConcreteStrategy object and maintains a reference to a Strategy object */
class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int a, int b) {
        return this.strategy.execute(a, b);
    }
}
/** Tests the pattern */
class StrategyExample {
    public static void main(String[] args) {
        Context context;

        // Three contexts following different strategies
        context = new Context(new Add());
        int resultA = context.executeStrategy(3, 4);

        context = new Context(new Subtract());
        int resultB = context.executeStrategy(3, 4);

        context = new Context(new Multiply());
        int resultC = context.executeStrategy(3, 4);
     
        System.out.println("Result A: " + resultA );
        System.out.println("Result B: " + resultB );
        System.out.println("Result C: " + resultC );
    }
}
在 Java 8 中实现

Java 中的示例

/** Imports a type of lambdas taking two arguments of the same type T and returning one argument of same type T */
import java.util.function.BinaryOperator;

/** Implements and assigns to variables the lambdas to be used later in configuring Context.
*   FunctionalUtils is just a convenience class, as the code of a lambda
*   might be passed directly to Context constructor, as for ResultD below, in main().
*/
class FunctionalUtils {
	static final BinaryOperator<Integer> add = (final Integer a, final Integer b) -> {
		System.out.println("Called add's apply().");
		return a + b;
	};

	static final BinaryOperator<Integer> subtract = (final Integer a, final Integer b) -> {
		System.out.println("Called subtract's apply().");
		return a - b;
	};

	static final BinaryOperator<Integer> multiply = (final Integer a, final Integer b) -> {
		System.out.println("Called multiply's apply().");
		return a * b;
	};
}
/** Configured with a lambda and maintains a reference to a lambda */
class Context {
	/** a variable referencing a lambda taking two Integer arguments and returning an Integer: */
	private final BinaryOperator<Integer> strategy;
    	
	public Context(final BinaryOperator<Integer> lambda) {
		strategy = lambda;
	}

	public int executeStrategy(final int a, final int b) {
		return strategy.apply(a, b);
	}
}
/** Tests the pattern */
public class StrategyExample {

	public static void main(String[] args) {
		Context context;

		context = new Context(FunctionalUtils.add);
		final int resultA = context.executeStrategy(3,4);

		context = new Context(FunctionalUtils.subtract);
		final int resultB = context.executeStrategy(3, 4);

		context = new Context(FunctionalUtils.multiply);
		final int resultC = context.executeStrategy(3, 4);

		context = new Context((final Integer a, final Integer b) -> a * b + 1);
		final int resultD = context.executeStrategy(3,4);

		System.out.println("Result A: " + resultA );
		System.out.println("Result B: " + resultB );
		System.out.println("Result C: " + resultC );
		System.out.println("Result D: " + resultD );
	}
}
在 JavaScript 中实现

类似于 Python 和 Scala,JavaScript 支持一等函数。以下代码实现了 Python 示例中看到的基本功能。

var Button = function(submit_func, label) {
    this.label = label;
    this.on_submit = submit_func;
};

var numbers = [1,2,3,4,5,6,7,8,9];
var sum = function(n) {
    var sum = 0;
    for ( var a in n ) {
        sum = sum + n[a];
    }
    return sum;
};

var a = new Button(sum, "Add numbers");
var b = new Button(function(numbers) {
    return numbers.join(',');
}, "Print numbers");

a.on_submit(numbers);
b.on_submit(numbers);
在 Perl 中实现

Perl 具有一等函数,因此与 Python、JavaScript 和 Scala 一样,此模式可以在不定义显式子类和接口的情况下实现

sort { lc($a) cmp lc($b) } @items

策略模式可以用Moose 正式实现

package Strategy;
use Moose::Role;
requires 'execute';

package FirstStrategy;
use Moose;
with 'Strategy';

sub execute {
    print "Called FirstStrategy->execute()\n";
}

package SecondStrategy;
use Moose;
with 'Strategy';

sub execute {
    print "Called SecondStrategy->execute()\n";
}

package ThirdStrategy;
use Moose;
with 'Strategy';

sub execute {
    print "Called ThirdStrategy->execute()\n";
}

package Context;
use Moose;

has 'strategy' => (
    is => 'rw',
    does => 'Strategy',
    handles => [ 'execute' ],  # automatic delegation
);

package StrategyExample;
use Moose;

# Moose's constructor
sub BUILD {
    my $context;

    $context = Context->new(strategy => 'FirstStrategy');
    $context->execute;

    $context = Context->new(strategy => 'SecondStrategy');
    $context->execute;

    $context = Context->new(strategy => 'ThirdStrategy');
    $context->execute;
}

package main;

StrategyExample->new;
在 PHP 中实现

PHP 中的策略模式

<?php
interface IStrategy {
    public function execute();
}

class Context {
    private $strategy;

    public function __construct(IStrategy $strategy) {
        $this->strategy = $strategy;
    }

    public function execute() {
        $this->strategy->execute();
    }
}

class ConcreteStrategyA implements IStrategy {
    public function execute() {
        echo "Called ConcreteStrategyA execute method\n";
    }
}

class ConcreteStrategyB implements IStrategy {
    public function execute() {
        echo "Called ConcreteStrategyB execute method\n";
    }
}

class ConcreteStrategyC implements IStrategy {
    public function execute() {
        echo "Called ConcreteStrategyC execute method\n";
    }
}

class StrategyExample {
    public function __construct() {
        $context = new Context(new ConcreteStrategyA());
        $context->execute();

        $context = new Context(new ConcreteStrategyB());
        $context->execute();

        $context = new Context(new ConcreteStrategyC());
        $context->execute();
    }
}

new StrategyExample();
?>
在 PowerShell 中实现

PowerShell 具有称为 ScriptBlocks 的一等函数,因此可以像 Python 一样对模式进行建模,将函数直接传递给上下文,而不是定义一个包含函数的类。

Function Context ([scriptblock]$script:strategy){
    New-Module -Name Context -AsCustomObject {
        Function Execute { & $strategy }
    }
}

$a = Context {'Style A'}
$a.Execute()

(Context {'Style B'}).Execute()

$c = Context {'Style C'}
$c.Execute()

使用 New-Module 的替代方法

Function Context ([scriptblock]$strategy){
    { & $strategy }.GetNewClosure()
}

$a = Context {'Style A'}
$a.Invoke()

& (Context {'Style B'})

$c = Context {'Style C'}
& $c
在 Python 中实现

以下示例等效于上面的 C# 示例 1,但在 Python 中。

import abc


class Bill:
    def __init__(self, billing_strategy: "BillingStrategy"):
        self.drinks: list[float] = []
        self.billing_strategy = billing_strategy

    def add(self, price: float, quantity: int) -> None:
        self.drinks.append(self.billing_strategy.get_act_price(price * quantity))

    def __str__(self) -> str:
        return f{sum(self.drinks)}"


class BillingStrategy(abc.ABC):
    @abc.abstractmethod
    def get_act_price(self, raw_price: float) -> float:
        raise NotImplementedError


class NormalStrategy(BillingStrategy):
    def get_act_price(self, raw_price: float) -> float:
        return raw_price


class HappyHourStrategy(BillingStrategy):
    def get_act_price(self, raw_price: float) -> float:
        return raw_price * 0.5


def main() -> None:
    normal_strategy = NormalStrategy()
    happy_hour_strategy = HappyHourStrategy()

    customer_1 = Bill(normal_strategy)
    customer_2 = Bill(normal_strategy)

    # Normal billing
    customer_1.add(2.50, 3)
    customer_1.add(2.50, 2)

    # Start happy hour
    customer_1.billing_strategy = happy_hour_strategy
    customer_2.billing_strategy = happy_hour_strategy
    customer_1.add(3.40, 6)
    customer_2.add(3.10, 2)

    # End happy hour
    customer_1.billing_strategy = normal_strategy
    customer_2.billing_strategy = normal_strategy
    customer_1.add(3.10, 6)
    customer_2.add(3.10, 2)

    # Print the bills;
    print(customer_1)
    print(customer_2)

if __name__ == "__main__":
    main()

Python 中的第二个示例

class Strategy:
    def execute(self, a, b):
        pass

class Add(Strategy):
    def execute(self, a, b):
        return a + b

class Subtract(Strategy):
    def execute(self, a, b):
        return a - b

class Multiply(Strategy):
    def execute(self, a, b):
        return a * b

class Context:
    def __init__(self, strategy):
        self.strategy = strategy

    def execute(self, a, b):
        return self.strategy.execute(a, b)

if __name__ == "__main__":
    context = None

    context = Context(Add())
    print "Add Strategy %d" % context.execute(10, 5)

    context = Context(Subtract())
    print "Subtract Strategy %d" % context.execute(10, 5)

    context = Context(Multiply())
    print "Multiply Strategy %d" % context.execute(10, 5)

在 Python 中的另一个示例:Python 具有一等函数,因此可以简单地将函数直接传递给上下文,而不是定义一个包含函数的方法的类,从而使用此模式。这样会丢失一些信息,因为策略的接口没有明确说明,但是通过这种方式简化了模式。

以下是在 GUI 编程中可能会遇到的示例,使用回调函数

class Button:
    """A very basic button widget."""
    def __init__(self, submit_func, label):
        self.on_submit = submit_func   # Set the strategy function directly
        self.label = label

# Create two instances with different strategies
button1 = Button(sum, "Add 'em")
button2 = Button(lambda nums: " ".join(map(str, nums)), "Join 'em")

# Test each button
numbers = range(1, 10)   # A list of numbers 1 through 9
print button1.on_submit(numbers)   # displays "45"
print button2.on_submit(numbers)   # displays "1 2 3 4 5 6 7 8 9"
在 Ruby 中实现

Ruby 中的示例

class Context
  def initialize(strategy)
    extend(strategy)
  end
end

module StrategyA
  def execute
     puts 'Doing the task the normal way'
  end
end

module StrategyB
  def execute
     puts 'Doing the task alternatively'
  end
end

module StrategyC
  def execute
     puts 'Doing the task even more alternatively'
  end
end

a = Context.new(StrategyA)
a.execute #=> Doing the task the normal way

b = Context.new(StrategyB)
b.execute #=> Doing the task alternatively

a.execute #=> Doing the task the normal way

c = Context.new(StrategyC)
c.execute #=> Doing the task even more alternatively

使用块

前面的 Ruby 示例使用了典型的 OO 特性,但可以使用 Ruby 的块以更少的代码实现相同的效果。

class Context
  def initialize(&strategy)
    @strategy = strategy
  end

  def execute
    @strategy.call
  end
end

a = Context.new { puts 'Doing the task the normal way' }
a.execute #=> Doing the task the normal way

b = Context.new { puts 'Doing the task alternatively' }
b.execute #=> Doing the task alternatively
 
c = Context.new { puts 'Doing the task even more alternatively' }
c.execute #=> Doing the task even more alternatively
在 Scala 中实现

与 Python 一样,Scala 也支持一等函数。以下代码实现了 Python 示例中显示的基本功能。

  // A very basic button widget.
  class Button[T](val label: String, val onSubmit: Range => T)
 
  val button1 = new Button("Add", _ reduceLeft (_ + _))
  val button2 = new Button("Join", _ mkString " ")
 
  // Test each button
  val numbers = 1 to 9  // A list of numbers 1 through 9
  println(button1 onSubmit numbers) // displays 45
  println(button2 onSubmit numbers) // displays 1 2 3 4 5 6 7 8 9


Clipboard

待办事项
添加更多插图。


状态 计算机科学设计模式
策略
模板方法


您对本页有任何疑问?
在这里提出问题


在本手册中创建一个新页面


华夏公益教科书