跳轉到內容

策略

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

待辦事項
新增更多插圖。


狀態 計算機科學設計模式
策略
模板方法


您對本頁有任何疑問?
在這裡提出問題


在本手冊中建立一個新頁面


華夏公益教科書