たくあんポリポリ

勉強したことを載せていきます。最近、技術系の記事はZennに書いています。(https://zenn.dev/chittai)

【C#】Strategy パターンについて調べてみる

デザインパターンの一つ、Strategyパターンについて勉強してみました。

ja.wikipedia.org

Strategyパターンとは何か

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

Wikipediaでは上記のように書かれています。実際に、使用されるメソッドをインタフェースで定義して、その具体的な処理は継承先で実装します。このかたまりを複数つくり、呼び出し元でどの継承したクラスを呼び出すのか選択できるようにすることで”アルゴリズムの切り替え”を実現しています。具体的な流れは下記実装にて確認していきます。

クラスの役割と処理の流れ

クラス図

これはWikipediaより。

f:id:c_taquna:20190223142221j:plain:w350

さらに今回の実装に合わせて書き直してみます。今回の処理は、前に書いたテンプレートパターンの記事(【C#】Template Methodパターンについて調べてみる - たくあんポリポリ)の処理を書き直しています。初期化→何かしらのメイン処理→クリーンアップという一連の処理になります。
f:id:c_taquna:20190223143159j:plain:w350

http://c-taquna.hatenablog.com/entry/2019/02/21/004830

インタフェース

まずは、インタフェースから見ていきます。メイン処理となるメソッドをここで定義します。

public interface ITestClass
{
    void Initialize();
    void DoSomething();
    void CleanUp();
}

処理内容

ここでは、具体的な処理の内容を記述します。今回はすごく簡単に標準出力で文字列を表示させているだけです。TestClass,TestClass2を定義しています。表される文字列が若干異なります。この2つのどちら処理を使用するのか切り替えることアルゴリズムの切り替えを実現しています。

class TestClass : ITestClass
{
    public void Initialize() 
    { 
        Console.WriteLine("InitializeA");
    }
    public void DoSomething()
    {
        Console.WriteLine("DoSomethingA");
    }
    public void CleanUp() 
    { 
        Console.WriteLine("CleanUpA");
    }
}

 

class TestClass2 : ITestClass
{
    public void Initialize() 
    { 
        Console.WriteLine("InitializeB");
    }
    public void DoSomething()
    {
        Console.WriteLine("DoSomethingB");
    }
    public void CleanUp() 
    { 
        Console.WriteLine("CleanUpB");
    }
}

一連の処理の実行 & 呼び出し

今回のポイント部分でもある処理の実行部分です。少し分解しながら流れを見ていきましょう。

class Runner
{
    ITestClass it;
    public Runner(ITestClass it)
    {
        this.it = it;
    }
    public void Run()
    {
        it.Initialize();
        it.DoSomething();
        it.CleanUp();
    }
}

呼び出しの処理です。

class Program
{
    static void Main(string[] args)
    {
        var run = new Runner(new TestClass());
        run.Run();
        var run2 = new Runner(new TestClass2());
        run2.Run();
    }
}


まずは、処理の呼び出しについてです。最初は、この様に、TestClassのインスタンスを引数にRunnerのインスタンスを作成します。

var run = new Runner(new TestClass());

そうすると、このコンストラクタにて、RunnerがもつITestClass型の引数itTestClassのインスタンスが格納されます。

public Runner(ITestClass it)
{
    this.it = it;
}

次に、呼び出し元のプログラムに戻ってみると、RunnerのRunメソッドを呼び出しています。

var run = new Runner(new TestClass());
run.Run();

Runメソッドはこの様になっています。itの各メソッドを呼び出しています。このitは先程書いたとおり、TestClassのインスタンスが格納されています。つまり、ここで実行されるのは、TestClassのメソッドです。

public void Run()
{
    it.Initialize();
    it.DoSomething();
    it.CleanUp();
}

つまり、itの中身を切り替える、Runnrerのインスタンスを作成するときの引数を切り替えることでメソッドが実行されるクラスを変更することができます。下記はTestClass2のインスタンスを引数にしたケースです。

var run2 = new Runner(new TestClass2());
run2.Run();

出力結果

InitializeA
DoSomethingA
CleanUpA
InitializeB
DoSomethingB
CleanUpB

メリット / デメリット

Strategyパターンのメリットは、依存関係にあると思ってます。Template Methodパターンでも同じようなことはできているのですが、具体的な処理を実装するクラスが抽象クラスに依存してしまっています。そのため、具象クラスの再利用が難しいのではないかと。あと、依存関係逆転の原則に準じているのでたくさんの汎用的な処理がかけるという部分かなと。デメリットは、Template Method パターンよりは複雑だったりするのでそういう点?

感想

メリット・デメリットついてもう少しちゃんと言及できるようになりたい