C#でストラテジーパターンを実装する

GoF(The Gang Of Four)の23デザインパターンのうちのひとつ、Strategy パターンをC#で実装する方法を紹介したいと思います。

増補改訂版 Java言語で学ぶデザインパターン入門

増補改訂版 Java言語で学ぶデザインパターン入門

このパターンは「利用側の実装(メソッド操作)を維持しながら」「複数の操作を切り替えることができる」のが特徴です。この特性を持たせるためにC#では2種類の方法を用いることができます。

インターフェースを用いたStrategy パターンの実装

まずはオーソドックスなインターフェースを継承する方法です。クラス図にすると以下のようになります。IHogeを継承した複数のクラスはそれぞれ動作が異なる実装を持ちます。

f:id:Takachan:20171208000954p:plain

上記コードが最も端的に利用されているケースは以下のコード例です。IHogeのインスタンスがDefaultHoge、HogeSampleの場合でも同じように利用できます。逆に言うと、今パターンの時は異なる使い方をさせてはダメという事になります。

複数の操作をインターフェースに記述することもできるので、複雑な操作があっても同じ実装で利用者が異なる処理を実行することができます。

// 利用側のコード
public void UserCode(IHoge hoge)
{
    string result = hoge.Func(1);
}

まぁただ、オブジェクトの生成を以下のようにハードコードしてしまうと意味がほどんど無いのでオブジェクトの生成はFactory パターンを使用したりするケースが多いです。

public void Main(string[] args)
{
    IHoge hoge = new DefaultHoge();
    hoge.Func(1);
}

デリゲートを用いたStrategy パターンの実装

2つ目の方法はC#固有機能であるデリゲート(古来関数ポインタと呼ばれていたもの)による実装です。デリゲートはシグネチャーさえ一致していればどんな関数でも運搬できる特徴を持っているのでこれを利用します。この場合処理が1つしか持てないので複雑なことには向きませんがメソッド単位などの比較的細かい処理でバリエーションをつける時に利用できます。

実装方法ですが、まず、以下のようにデリゲートを宣言します。

public delegate string FuncDelegate(int param); // 引数にintを持ち、戻り値にstringを返すデリゲート

そのあとに以下のように「intを引数に取り戻り値にstringを返す処理を返す」複数の操作を取得するメソッドを作成します。

// modeによって処理の違うFuncDelegateを生成するメソッド
public FuncDelegate CreateFunc(int mode)
{
    if (mode == 0)
    {
        return param => "Mode=0";
    }
    else if (mode == 1)
    {
        return param => "Mode=1";
    }

    throw new NotSupportedException($"This mode is not suported. mode={mode}");
}

そうすると利用者側でシグネチャを変更することなく複数の操作を同じ実装で実行することができます。

public void Foo(FuncDelegte func)
{
    string ret = func(1); // これで Mode=0, Mode=1のように受け取るデリゲートで表示が変わる
}

Strategyパターンの説明は以上です。