C# Programming

Image

C# Design Patterns

開発環境: Visual Studio 2003

目的

注意: このコーナーはデザインパターンの解説を目的にしたものではありません。
デザインパターンのスケルトンを用意しておくことにより、楽をしようというものです。
デザインパターンの解説については、参考書(1)を読んでください。

目次

  1. 目的
  2. 目次
  3. デザインパターンとは?
  4. 参考書
  5. C# での Design Pattern スケルトン
  6. 生成に関するパターン
    1. Abstract Factory のスケルトンコード (抽象クラスを使用)
    2. Singleton のスケルトンコード
  7. 構造に関するパターン
    1. Adapter (インターフェースを利用)
  8. 振る舞いに関するパターン
    1. Iterator
  9. その他

デザインパターンとは?

オブジェクト指向のプログラムを作っていると同じようなプログラムパターンが出てきます。
これらを集めたものが Design Patter であると言っていいと思います。

参考書

(1) オブジェクト指向における再利用のためのデザインパターン(改定版)
ISBN4-7973-1112-6
いわゆる GoF ( Gang of Four と呼ばれる、Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) 著

この本はデザインパターンのバイブルですが、C++, Smalltalk を例にとり書かれているし、わかりにくい。

C# での Design Pattern スケルトン

GoF では、次のような23の Design Pattern を定義しています。

生成に関するパターン

Abstract Factory のスケルトンコード (抽象クラスを使用)


Factory.cs
namespace Uchukamen.Factory
{
        /// <summary>
        /// AbstractFactory
        /// </summary>
        abstract class AbstractFactory
        {
                public abstract AbstractProductA CreateProductA();              
                public abstract AbstractProductB CreateProductB();
        }
}
Factory1.cs
namespace Uchukamen.Factory
{
        class ConcreteFactory1: AbstractFactory
        {
                public override AbstractProductA CreateProductA()
                {
                        AbstractProductA apA = new ProductA1();
                        return apA;
                }
                public override AbstractProductB CreateProductB()
                {
                        AbstractProductB apB = new ProductB1();
                        return apB;
                }
        }
}
Factory2.cs
namespace Uchukamen.Factory
{
        class ConcreteFactory2: AbstractFactory
        {
                public override AbstractProductA CreateProductA()
                {
                        AbstractProductA apA = new ProductA2();
                        return apA;
                }
                public override AbstractProductB CreateProductB()
                {
                        AbstractProductB apB = new ProductB2();
                        return apB;
                }
        }
}
Product.cs
namespace Uchukamen.Factory
{
        /// <summary>
        /// AbstractProductA
        /// </summary>
        abstract class AbstractProductA
        {
                public abstract void PrintName();
        }

        /// <summary>
        /// AbstractProductB
        /// </summary>
        abstract class AbstractProductB
        {
                public abstract void PrintName();
        }
}
ProductA.cs
using System;

namespace Uchukamen.Factory
{
        class ProductA1: AbstractProductA
        {
                public override void PrintName()
                {
                        Console.WriteLine("ProductA1");
                }
        }

        class ProductA2: AbstractProductA
        {
                public override void PrintName()
                {
                        Console.WriteLine("ProductA2");
                }
        }
}
ProductB.cs
using System;

namespace Uchukamen.Factory
{
        class ProductB1: AbstractProductB
        {
                public override void PrintName()
                {
                        Console.WriteLine("ProductB1");
                }
        }

        class ProductB2: AbstractProductB
        {
                public override void PrintName()
                {
                        Console.WriteLine("ProductB2");
                }
        }
}
main.cs
using System;

namespace Uchukamen.Factory
{
        class Class1
        {
                [STAThread]
                static void Main(string[] args)
                {
                //      AbstractFactory af = new ConcreteFactory1();
                        AbstractFactory af = new ConcreteFactory2();
                        AbstractProductA apA = af.CreateProductA();
                        AbstractProductB apB = af.CreateProductB();
                        apA.PrintName();
                        apB.PrintName();

                        Console.ReadLine();
                }
        }
}

Singleton のスケルトンコード

シングルトンでは、1つのインスタンスしか生成しないようにします。

1. Singleton クラスは、sealed class とすることにより、継承による override を防ぐ。
2. コンストラクタをプライベートとし、New 演算子を使用してインスタンスを生成をできないようにする。
3. GetInstance() メソッドではなく、static readonly のInstance プロパティでインスタンスを生成します。これにより、JITプロセスにより最初の static メソッドが呼ばれた時点で遅延初期化(Lazy initialization) される。
4. .Net Framework は、static type に対して、thread-safe を保障する。したがって、lock などのマルチスレッドセーフの仕組みを組み込む必要なない。

参考: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/factopattern.asp
using System;

namespace Singleton
{
        /// <summary>
        /// sealed class とすることにより、継承によるoverride を防ぐ。
        /// </summary>
        sealed class Singleton 
        {
                /// <summary>
                /// new でインスタンスを生成できないように、private コンストラクタで宣言する。
                /// </summary>
                private Singleton() 
                {
                        Console.WriteLine("An instance has been created!");
                }

                public static readonly Singleton Instance = new Singleton();
        
                public void DoSomething()
                {
                        Console.WriteLine("DoSomething!");
                }
        }

        class Tester
        {
                [STAThread]
                static void Main(string[] args)
                {
                        Singleton.Instance.DoSomething();

                        Console.ReadLine();
                }
        }
}

構造に関するパターン

Adapter (インターフェースを利用)


Adaptee.cs / 再利用しようとしているクラス
using System;

namespace Adapter
{
        class Adaptee
        {
                public void MethodA()
                {
                        Console.WriteLine("MethodA");
                }
                public void MethodB()
                {
                        Console.WriteLine("MethodB");
                }
        }
}
Adapter.cs / インターフェースを適合させるクラス。
namespace Adapter
{
        interface Target
        {
                void TargetMethod1();
                void TargetMethod2();
        }

        class Adapter : Adaptee, Target
        {
                public void TargetMethod1()
                {
                        MethodA();
                }

                public void TargetMethod2()
                {
                        MethodB();
                }
        }
}
Main.cs
TargetMethod1(), TargetMethod2() という新しいメソッドで、再利用するクラス Adaptee のMethodA(), MethodB() を利用することができる。
古いクラス Adaptee は、変更せずにそのまま使える。
using System;

namespace Adapter
{
        class Class1
        {
                [STAThread]
                static void Main(string[] args)
                {
                        Adapter adapter = new Adapter();

                        adapter.TargetMethod1();
                        adapter.TargetMethod2();

                        Console.ReadLine();
                }
        }
}

振る舞いに関するパターン

Iterator

C# では、Iterator に相当する IEnumerator インターフェース、Aggregate に相当する IEnumerable インターフェースが用意されていています。
また、IEnumerable から継承する ICollection, IList もあり、下表のようにかなりのクラスでサポートされています。
このため、GoF本のように Aggregate, Iterator を作るよりは、IEnumerator, IEnumerable インターフェースを利用するほうが再利用の観点から望ましいでしょう。


IEnumerator, IEnumerable インターフェースは次のような定義です。

IEnumerable インターフェース
public interface IEnumerable

IEnumerator GetEnumerator();
IEnumerator インターフェース
public interface IEnumerator

object Current {get;}
bool MoveNext();
void Reset();
IEnumerator を実装するクラスに対しては、foreach を使う方法と、IEnumerator を使う方法の2つが可能です。
foreach を使う方法
ArrayList myArrayList = new ArrayList();
myArrayList.Add("Hello");
myArrayList.Add("World");
myArrayList.Add(123);

foreach (object obj in myArrayList)
{
Console.WriteLine(obj);
}
IEnumerator を使う方法
ArrayList myArrayList = new ArrayList();
myArrayList.Add("Hello");
myArrayList.Add("World");
myArrayList.Add(123); IEnumerator myEnum = myArrayList.GetEnumerator();
while (myEnum.MoveNext())
{
Console.WriteLine(myEnum.Current);
}
注意: Java のIterator では、hasNext(), Next() のペアを使用しますが、
C# では 最初の MoveNext() を実行後に第1番目の要素にアクセス可能な場所に移動します。

また、IEnumerator, IEnumerable を実装することにより、独自に作成したクラスに対して foreach によるアクセスが可能になります。
このように、IEnumerator を分離したインターフェースとすることにより、Enumeration のロジックを分離することができます。つまり、オーバーライドすることにより、たとえば逆順でリストするようなことも可能になります。
IEnumerator, IEnumerable を実装する。
                public class MyEnumerable : IEnumerable
                {
                        private static object[] myArray = new object[3];
        
                        public MyEnumerable()
                        {
                                myArray[0] = "Hello";
                                myArray[1] = "World";
                                myArray[2] = 123;
                        }

                        public IEnumerator GetEnumerator()
                        {
                                return new MyEnumerator (this);
                        }

                        class MyEnumerator : IEnumerator
                        {
                                private int current = -1;
                                private MyEnumerable myEnumerable;

                                public MyEnumerator(MyEnumerable enumerable)
                                {
                                        myEnumerable = enumerable;
                                }

                                public bool MoveNext()
                                {
                                        current++;
                                        if ( current < myArray.Length)
                                        {
                                                return true;
                                        }
                                        else
                                        {
                                                return false;
                                        }
                                }

                                public object Current
                                {
                                        get
                                        {
                                                return myArray[current];
                                        }
                                }

                                public void Reset()
                                {
                                        current = -1;
                                }
                        }
                }
呼び出し方
                        MyEnumerable myEnumerable = new MyEnumerable();

                        foreach (object obj in myEnumerable)
                        {
                                Console.WriteLine(obj);
                        }
また、ICollection, IList インターフェースは、IEnumerable から継承していますので、ICollection, IList を実装するクラスに対しても同様にアクセスすることが可能です。

ICollection インターフェース
public interface ICollection : IEnumerable

int Count {get;}
bool IsSynchronized {get;}
object SyncRoot {get;}
void CopyTo( Array array, int index );
IList インターフェース
public interface ICollection : IEnumerablebool IsFixedSize {get;}

bool IsReadOnly {get;}
object this[ int index ] {get; set;}
int Add( object value );
void Clear();
bool Contains( object value );
int IndexOf( object value );
void Insert( int index, object value );
void Remove( object value );
void RemoveAt( int index );

.NET インフラストラクチャで IEnumerable をサポートしているクラスは相当の数になります。
まずは、これらのクラスから継承できないか検討してみるのが良いでしょう。
どうしても再利用できない場合に限り、IEnumerable を実装するようにしたほうがいいでしょう。
Iterator のサンプルコード
using System;
using System.Collections;

namespace Iterator1
{
        /// <summary>
        /// Class1 の概要の説明です。
        /// </summary>
        class Class1
        {

                public class MyEnumerable : IEnumerable
                {
                        private static object[] myArray = new object[3];
        
                        public MyEnumerable()
                        {
                                myArray[0] = "Hello";
                                myArray[1] = "World";
                                myArray[2] = 123;
                        }

                        public IEnumerator GetEnumerator()
                        {
                                return new MyEnumerator (this);
                        }

                        class MyEnumerator : IEnumerator
                        {
                                private int current = -1;
                                private MyEnumerable myEnumerable;

                                public MyEnumerator(MyEnumerable enumerable)
                                {
                                        myEnumerable = enumerable;
                                }

                                public bool MoveNext()
                                {
                                        current++;
                                        if ( current < myArray.Length)
                                        {
                                                return true;
                                        }
                                        else
                                        {
                                                return false;
                                        }
                                }

                                public object Current
                                {
                                        get
                                        {
                                                return myArray[current];
                                        }
                                }

                                public void Reset()
                                {
                                        current = -1;
                                }
                        }
                }
                
                [STAThread]
                static void Main(string[] args)
                {
                        ArrayList myArrayList = new ArrayList();
                        myArrayList.Add("Hello");
                        myArrayList.Add("World");
                        myArrayList.Add(123);

                        Console.WriteLine("Enumerate by foreach");
                        foreach (object obj in myArrayList)
                        {
                                Console.WriteLine(obj);
                        }

                        Console.WriteLine("\nEnumerate by IEnumerator");
                        IEnumerator myEnum = myArrayList.GetEnumerator();
                        while (myEnum.MoveNext())
                        {
                                Console.WriteLine(myEnum.Current);
                        }

                        MyEnumerable myEnumerable = new MyEnumerable();

                        Console.WriteLine("\nMyEnumerable by foreach");
                        foreach (object obj in myEnumerable)
                        {
                                Console.WriteLine(obj);
                        }

                        Console.ReadLine();
                }
        }
}