かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

C# 4.0で実装するデザインパターン「その1 生成に関するパターン」

気が向いたやつをやってみます。最初はGoFの全パターンをやってみようと思いましたが、あまり従来と変わらないものとかもあるので、これはちょっと実装が変わるかなと思ったものをピックアップしていくつもりです。。
ちなみに、参考にしてるのは以下のサイトです。

なるべく上記のサイトと同等のコードになるように努めています。
今回は、とりあえず生成に関するパターンを見てみようと思います。


生成に関するパターン

Factory Methodパターン

これは、オブジェクトを作るクラスを継承関係を使って・・・とめんどくさいことをしていますが、メソッドを変数に入れて扱うというデリゲートの仕組みがあるC#では、あえて生成メソッドのためだけにクラスをこしらえる必要はないと思ったりします。

ということで、以下のような感じになりました。

namespace Okazuki.GoFPatterns.FactoryMethod
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            // Creatorが返すものはAかBか意識する必要はない
            var creator1 = Create("A");
            var creator2 = Create("B");

            // デリゲートなのでそのまま呼び出し
            var product1 = creator1();
            var product2 = creator2();

            product1.Operation();
            product2.Operation();
        }

        // ファクトリメソッド?を作るメソッド
        private static Func<Product> Create(string name)
        {
            // これくらいの規模だとラムダが楽でいいよね
            if (name == "A")
            {
                return () => new ProductA();
            }
            if (name == "B")
            {
                return () => new ProductB();
            }
            throw new ArgumentException();
        }
    }

    public abstract class Product
    {
        public abstract void Operation();
    }
    public class ProductA : Product
    {
        public override void Operation()
        {
            Console.WriteLine("ProductA");
        }
    }
    public class ProductB : Product
    {
        public override void Operation()
        {
            Console.WriteLine("ProductB");
        }
    }
}
AbstractFactoryパターン

これは、生成対象のクラスの種類が増えただけでFactoryMetohdパターンとあまり変わらない気がします。ということで、ここでも生成処理はデリゲートを使って差し替え可能なようにしてみました。
こうすると生成するオブジェクトの組み合わせが自由自在ですからね。
(継承だと組み合わせパターンの数だけファクトリクラスを作らないといけない)

namespace Okazuki.GoFPatterns.AbstractFactory
{
    using System;

    class Program
    {
        static void Main(string[] args)
        {
            var factory1 = Create("A");
            var a1 = factory1.CreateProductA();
            var b1 = factory1.CreateProductB();
            a1.Operation();
            b1.Operation();

            var factory2 = Create("B");
            var a2 = factory2.CreateProductA();
            var b2 = factory2.CreateProductB();
            a2.Operation();
            b2.Operation();
        }

        static AbstractFactory Create(string name)
        {
            // AbstractFactoryに生成処理を差し込んで返す
            if (name == "A")
            {
                return new AbstractFactory(
                    () => new ProductA1(),
                    () => new ProductB1());
            }
            if (name == "B")
            {
                return new AbstractFactory(
                    () => new ProductA2(),
                    () => new ProductB2());
            }
            throw new ArgumentException();
        }
    }

    // 生成処理はデリゲートで差し替え可能にしておく
    class AbstractFactory
    {
        public Func<ProductA> CreateProductA { get; private set; }
        public Func<ProductB> CreateProductB { get; private set; }

        public AbstractFactory(Func<ProductA> createProductA,
            Func<ProductB> createProductB)
        {
            this.CreateProductA = createProductA;
            this.CreateProductB = createProductB;
        }
    }

    abstract class ProductA
    {
        public abstract void Operation();
    }
    abstract class ProductB
    {
        public abstract void Operation();
    }

    class ProductA1 : ProductA
    {
        public override void Operation()
        {
            Console.WriteLine("ProductA1");
        }
    }
    class ProductA2 : ProductA
    {
        public override void Operation()
        {
            Console.WriteLine("ProductA2");
        }
    }

    class ProductB1 : ProductB
    {
        public override void Operation()
        {
            Console.WriteLine("ProductB1");
        }
    }
    class ProductB2 : ProductB
    {
        public override void Operation()
        {
            Console.WriteLine("ProductB2");
        }
    }
}
Builderパターン

これは、オブジェクトの生成手順と生成手段を分離するパターンみたいです。
まぁ、組み立てるのが複雑なオブジェクトを作るためのお助けクラスのビルダー君を用意してあげましょうということだと理解してます。

これは、そんなに実装は変わらないと思いますがC#だとそんなにビルダーが必要なほど組み立てが厄介なクラスは少ないかもしれません。リンク先では木構造を持つオブジェクトを組み立てるサンプルをやっていますが、同じ木構造を組み立てるための機能を持つC#のLINQ to XMLのXElement何かをみてみるとビルダーなんか使わなくてもサクサク木構造を組み立てれるようになってます。

使用する言語の機能としてはC# 3.0で追加されたオブジェクト初期化子やコレクション初期化子可変長引数あたりを使う方法があります。

LINQ to XMLで木構造のオブジェクトを作るのがどれだけ簡単になるかという例は以下を見てみてください。記事の一番下らへんに書いてあります。
C#3.0の新機能を試す(LINQ,ラムダ式,暗黙的型付け, etc...)

とまぁ、こんな風にビルダーパターンを使わないといけないケースっていうのは減ってきてると思います。他言語ではビルダーパターンを使ってるケースでもC#4.0では案外簡易的に書けるかもしれないということを覚えておくといいと思います。

Prototypeパターン

個人的にC#4.0だからといって特別に言うことはないと思います。愚直に実装しましょう。

Singletonパターン

これは、よくC#ではstaticクラスがあるんだぜ!!と声高に言われるパターンですね。わざわざ、言語が持つ色々な仕組みを駆使してオブジェクトのインスタンスが1つであることを保証するまでもないということです。

コード例では、以下のようなかんじですね。

static class Singleton
{
  public static void Operation()
  {
    Console.WriteLine("Singleton");
  }
}

正直、個人的にはこれはシングルトンじゃなくて以下のJavaのコードと同じ意味のコードだと思ったりします。

class Singleton {
  private Singleton() { }
  
  public static void Operation() {
    System.out.println("Singleton");
  }
}

なので、Javaとかで書かれてるSingletonと同質のコードを書こうと思ったらJavaと同じような感じで書かないといけないと思います。

class Singleton
{
    public static Singleton Instance { get; private set; }

    static Singleton()
    {
        Instance = new Singleton();
    }

    private Singleton() { }

    public void Operation()
    {
        Console.WriteLine("Singleton");
    }
}

あと、個人的にですがシングルトンパターンは厳密にインスタンスが1つしかないとしてしまうと、単体テストの時にシングルトンのクラスをモックに差し替えるのが非常に困難になってしまいます。なので、抜け道を作っておくか、DIコンテナにインスタンス管理は任せてしまって、プログラム的にはインスタンスが1つだという制約は設けないというのも1つの手だと思います。