Strategy パターン

Strategy パターン(ストラテジーパターン)は、アルゴリズムを共通のインターフェースを持つ独立したクラスとして定�…

Strategy パターン

Strategy パターン(ストラテジーパターン)は、アルゴリズムを共通のインターフェースを持つ独立したクラスとして定義し、実行時に交換可能にすることで、処理内容の変更を利用側のコードから独立させるデザインパターンである。これにより、複数のアルゴリズムを状況に応じて切り替えながら、利用側の構造を変更せずに拡張や保守を行えるようになる。

Strategy パターンを示す図

UML で表した Strategy パターン

サンプルコード

Java

Javaではクラスメソッドオーバーライドによるポリモーフィズムを使ってStrategyパターンを実現することができる。インターフェイスを用いた例を示す。

package org.wikipedia.patterns.strategy;

// MainApp test application
class MainApp {
    public static void main(String[] args) {
        Context context;

        // 異なるアルゴリズムに従う3つのコンテキスト。
        context = new Context(new ConcreteStrategyA());
        context.execute();

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

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

// 具体的な戦略を実装するクラスは、このインターフェイスを実装する。
// コンテキストクラスは、具体的な戦略を呼び出すためにこのインターフェイスを使用する。
interface Strategy {
    void execute();
}

// Strategy インターフェイスを用いたアルゴリズムの実装。
class ConcreteStrategyA implements Strategy {
    public void execute() {
        System.out.println("Called ConcreteStrategyA.execute()");
    }
}

class ConcreteStrategyB implements Strategy  {
    public void execute() {
        System.out.println("Called ConcreteStrategyB.execute()");
    }
}

class ConcreteStrategyC implements Strategy {
    public void execute() {
        System.out.println("Called ConcreteStrategyC.execute()");
    }
}

// ConcreteStrategy を指定して作成され、Strategy オブジェクトへの参照を保持する。
class Context {
    Strategy strategy;

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

    public void execute() {
        this.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

# 異なる戦略を持つ2つのインスタンスを作成
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#

C#はJava同様にクラスやインターフェイスによるポリモーフィズムを用いることもできるが、カスタマイズポイントがひとつのメソッドしかない場合(オブジェクトの他のプロパティやメソッドにアクセスしない場合)は、継承関係を必要としないデリゲートを使うほうが好まれる[1]

using System;

// MainApp テストアプリケーション。
public class MainApp
{
    public static void Main()
    {
        Context context;

        // 異なるアルゴリズムに従う3つのコンテキスト。
        context = new Context(new ConcreteStrategyA().Execute);
        context.Execute();

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

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

// 具体的な戦略を実装するクラスは、このデリゲートに適合するメソッドを実装する。
// コンテキストクラスは、具体的な戦略を呼び出すためにこのデリゲートを使用する。
public delegate void ExecuteStrategyDelegate();

class ConcreteStrategyA
{
    public void Execute()
    {
        Console.WriteLine("Called ConcreteStrategyA.Execute()");
    }
}

class ConcreteStrategyB
{
    public void Execute()
    {
        Console.WriteLine("Called ConcreteStrategyB.Execute()");
    }
}

class ConcreteStrategyC
{
    public void Execute()
    {
        Console.WriteLine("Called ConcreteStrategyC.Execute()");
    }
}

// ExecuteStrategyDelegate オブジェクトへの参照を保持する。
class Context
{
    ExecuteStrategyDelegate executeStrategy;

    // Constructor
    public Context(ExecuteStrategyDelegate executeStrategy)
    {
        this.executeStrategy = executeStrategy;
    }

    public void Execute()
    {
        this.executeStrategy();
    }
}

なお、Javaもバージョン8以降であれば、メソッド参照と関数型インターフェイス (functional interface) を用いることで、C#と類似の実装が可能となる。

package org.wikipedia.patterns.strategy;

// MainApp test application
class MainApp {
    public static void main(String[] args) {
        // Strategy インターフェイスを用いたアルゴリズムの実装。
        // 元々はConcreteStrategyとして明示的に定義していたが、必要なくなっている。
        Strategy concreteStrategyA = ()-> System.out.println("Called ConcreteStrategyA.execute()");
        Strategy concreteStrategyB = ()-> System.out.println("Called ConcreteStrategyB.execute()");
        Strategy concreteStrategyC = ()-> System.out.println("Called ConcreteStrategyC.execute()");
        
        Context context;

        // 異なるアルゴリズムに従う3つのコンテキスト。
        context = new Context(concreteStrategyA);
        context.execute();

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

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

// 具体的な戦略を実装するクラスは、このインターフェイスを実装する。
// コンテキストクラスは、具体的な戦略を呼び出すためにこのインターフェイスを使用する。
interface Strategy {
    void execute();
}

// ConcreteStrategy を指定して作成され、Strategy オブジェクトへの参照を保持する。
class Context {
    Strategy strategy;

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

    public void execute() {
        this.strategy.execute();
    }
}

Strategy パターンと開放/閉鎖原則

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

アクセルとブレーキの振る舞いは車種により異なる場合があるため、良くあるやり方はこれらの振る舞いをCarのサブクラスとして実装することである。が、このやり方には大きな問題点がある。それはアクセルとブレーキの振る舞いが車種間で同じでも、車種ごとに新たに宣言・定義し直されなければならない事である。これは車種が少ないときには小さな問題で済むが、車種が増えるにつれ、それらの振る舞いを管理する作業とコード重複量が大幅に増えてしまうことになる。さらに、各コードを詳しく分析しなければ各車種の振る舞いの性質を知ることができない。

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

brakeBehavior = new Brake(); 

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

脚注

関連項目

外部リンク

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.