Strategy パターン

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索

Strategy パターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。

ポリモーフィズムを持たないようなプログラミング言語では、このパターンによって解決される問題は、関数ポインタ委譲を使った記述によりリフレクションの形態で扱われる。

このパターンは、関数が第一級オブジェクトである言語では暗黙のうちに使用されている。例として Python コード を参照のこと。

Strategy パターンは、アプリケーションで使用されるアルゴリズムを動的に切り替える必要がある際に有用である。Strategy パターンはアルゴリズムのセットを定義する方法を提供し、これらを交換可能にすることを目的としている。Strategy パターンより、アルゴリズムを使用者から独立したまま様々に変化させることができるようになる。

Strategy パターンを示す図[編集]

UML で表した Strategy パターン

サンプルコード[編集]

Java[編集]

package org.wikipedia.patterns.strategy;
 
// MainApp test application
class MainApp {
 
  public static void main(String[] args) {
    Context context;
 
    // 異なるアルゴリズムに従う三つのコンテキスト
    context = new Context(new ConcreteStrategyA());
    context.execute();
 
    context = new Context(new ConcreteStrategyB());
    context.execute();
 
    context = new Context(new ConcreteStrategyC());
    context.execute();
  }
}
  // 具体的な戦略を実装するクラスは、このインターフェイスを実装する。
  // コンテキストクラスは、具体的な戦略を呼び出すためにこのインターフェイスを使用する
  interface IStrategy {
    void execute();
  }
 
  // strategyインターフェイスを用いたアルゴリズムの実装
  class ConcreteStrategyA implements IStrategy {
    public void execute() {
      System.out.println( "Called ConcreteStrategyA.execute()" );
    }
  }
 
  class ConcreteStrategyB implements IStrategy  {
    public void execute() {
      System.out.println( "Called ConcreteStrategyB.execute()" );
    }
  }
 
  class ConcreteStrategyC implements IStrategy {
    public void execute() {
      System.out.println( "Called ConcreteStrategyC.execute()" );
    }
  }
 
  // ConcreteStrategy を指定して作成され、Strategyオブジェクトへの参照を保持する
  class Context {
    IStrategy strategy;
 
    // Constructor
    public Context(IStrategy strategy) {
      this.strategy = strategy;
    }
 
    public void execute() {
      strategy.execute();
    }
  }

Python[編集]

Python では関数が第一級オブジェクトであり、このパターンを明示的に定義する必要はない。下記はコールバック関数を用いる GUI プログラミングで見られる例である。

class Button:
    """A very basic button widget."""
    def __init__(self, submit_func, label):
        self.on_submit = submit_func   # strategy 関数を直接生成
        self.label = label
 
# 異なる戦略を持つ二つのインスタンスを作成
button1 = Button(sum, "Add 'em")
button2 = Button(lambda nums: " ".join(map(str, nums)), "Join 'em")
 
# ボタンをテストする
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"

C#[編集]

using System;
 
namespace Wikipedia.Patterns.Strategy
{
  // MainApp テストアプリケーション
  class MainApp
  {
    static void Main()
    {
      Context context;
 
      // 異なるアルゴリズムに従う三つのコンテキスト
      context = new Context(new ConcreteStrategyA());
      context.Execute();
 
      context = new Context(new ConcreteStrategyB());
      context.Execute();
 
      context = new Context(new ConcreteStrategyC());
      context.Execute();
 
    }
  }
 
  // 具体的な戦略を実装するクラスは、このインターフェイスを実装する。
  // コンテキストクラスは、具体的な戦略を呼び出すためにこのインターフェイスを使用する
  interface IStrategy
  {
    void Execute();
  }
 
  // strategy インターフェイスを用いてアルゴリズムを実装
  class ConcreteStrategyA : IStrategy
  {
    public void Execute()
    {
      Console.WriteLine( "Called ConcreteStrategyA.Execute()" );
    }
  }
 
  class ConcreteStrategyB : IStrategy
  {
    public void Execute()
    {
      Console.WriteLine( "Called ConcreteStrategyB.Execute()" );
    }
  }
 
  class ConcreteStrategyC : IStrategy
  {
    public void Execute()
    {
      Console.WriteLine( "Called ConcreteStrategyC.Execute()" );
    }
  }
 
  // ConcreteStrategy を指定して作成され、Strategyオブジェクトへの参照を保持する
  class Context
  {
    IStrategy strategy;
 
    // Constructor
    public Context(IStrategy strategy)
    {
      this.strategy = strategy;
    }
 
    public void Execute()
    {
      strategy.Execute();
    }
  }
}

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();
	}
    }
}

Strategy パターンと Bridge パターン[編集]

Strategy パターンの UML クラス図は Bridge パターンのものと同じである。しかし、これら二つのデザインパターンはその意図が同じではない。Strategy パターンは 振る舞い に対するパターンであるが、Bridge パターンは 構造 に対するパターンである。

一般にコンテキストと戦略との結合は、Bridge パターンにおける抽象化と実装の結合より強固である。

Strategy パターンと開放/閉鎖原則[編集]

Strategy パターンに従うと、クラスの振る舞いは継承されるべきではなく、インターフェイスを用いてカプセル化するべきである。例として Car クラスを考えると、Car の振る舞いにはブレーキとアクセルがある。

アクセルとブレーキの振る舞いはモデルにより大きく異なるため、良くあるアプローチはこれらの振る舞いをサブクラスとして実装することであるが、このアプローチには大きな問題点がある。すなわち、アクセルとブレーキの振る舞いは、新たな Car モデルごとに宣言されなければならない。これはモデルが少ないときには問題にならないが、モデルの数が増えるにつれ、それらの振る舞いを管理する作業が大幅に増加し、またコードがモデル間で重複することになる。さらに、各コードを詳しく分析しなければそれぞれのモデル用の振る舞いの性質を知ることができない。

これに対して Strategy パターンでは、継承ではなく合成を用いる。Strategy パターンにおける振る舞いは別々のインターフェイスと、これらのインターフェイスを実装した抽象クラスとして定義される。具体的なクラスは、これらのインターフェイスをカプセル化する。これにより、振る舞いと、それを用いるクラスがうまく分離できる。振る舞いは、それを用いるクラスに変更を加えずに変更することができ、クラスは大きなコード変更を必要とすることなく、使用する実装を切り替えることで振る舞いを切り替えることができる。振る舞いは設計時にも実行時にも変更することができる。例として、Car オブジェクトのブレーキの振る舞いを、メンバー brakeBehavior を BrakeWithABS から Brake に変えることで変更できる:

brakeBehavior = new Brake();

Strategy.JPG

これにより設計に優れた柔軟性をもたせることができ、かつ拡張に対して開放的であり変更に対して閉鎖的であるべきとする OCP (Open Closed Principle, 開放/閉鎖原則)とも調和を保つことができる。

関連項目[編集]

外部リンク[編集]