デザインパターンは通常、次の3つの主要カテゴリに分類されます。
- 生成(Creational)パターン
- 構造(Structural)パターン
- 振る舞い(Behavioral)パターン
これらのパターンは、一般的なオブジェクト指向設計の問題を解決するためのベストプラクティスです。
以下は、よく知られている23のデザインパターンと、それぞれのC#コード例です。
生成(Creational)パターン
1. シングルトンパターン(Singleton)
public sealed class Singleton
{
// 読み取り専用の静的Singletonインスタンスを作成
private static readonly Singleton instance = new Singleton();
// Singletonの作成回数を記録
private static int instanceCounter = 0;
// Singletonインスタンスへのパブリックアクセスポイント
public static Singleton Instance
{
get
{
return instance;
}
}
// プライベートコンストラクタ
private Singleton()
{
instanceCounter++;
Console.WriteLine("Instances Created " + instanceCounter);
}
// ここに他のSingletonクラスメソッドを追加
public void LogMessage(string message)
{
Console.WriteLine("Message: " + message);
}
}
この例では、Singletonクラスはプライベートコンストラクタと静的読み取り専用プロパティInstanceを持ち、唯一のインスタンスへのアクセスを提供します。また、LogMessageメソッドでSingletonクラスの動作をシミュレートしています。
以下は、このSingletonクラスを使用するコンソールアプリケーションの例です。
class Program
{
static void Main(string[] args)
{
Singleton fromEmployee = Singleton.Instance;
fromEmployee.LogMessage("Message from Employee");
Singleton fromBoss = Singleton.Instance;
fromBoss.LogMessage("Message from Boss");
Console.ReadLine();
}
}
2. ファクトリメソッドパターン(Factory Method)
ファクトリメソッドパターンは、オブジェクトを作成するためのインターフェースを提供しますが、サブクラスがインスタンス化するクラスを決定できるようにします。これにより、クラスのインスタンス化をサブクラスに委譲します。
以下は、C#でファクトリメソッドパターンを実装したシンプルな例です。
// 抽象プロダクト
public interface IProduct
{
string Operation();
}
// 具象プロダクトA
public class ProductA : IProduct
{
public string Operation()
{
return "{Result of ProductA}";
}
}
// 具象プロダクトB
public class ProductB : IProduct
{
public string Operation()
{
return "{Result of ProductB}";
}
}
// 抽象クリエイター
public abstract class Creator
{
public abstract IProduct FactoryMethod();
}
// 具象クリエイターA
public class CreatorA : Creator
{
public override IProduct FactoryMethod()
{
return new ProductA();
}
}
// 具象クリエイターB
public class CreatorB : Creator
{
public override IProduct FactoryMethod()
{
return new ProductB();
}
}
上記のコードでは、ProductAとProductBという2つのプロダクトが定義され、どちらもIProductインターフェースを実装しています。次に、CreatorAとCreatorBという2つのCreatorクラスがあり、これらは抽象基底クラスCreatorを継承しています。CreatorAはProductAを生成し、CreatorBはProductBを生成します。
以下は、これらのファクトリとプロダクトを使用する例です。
class Program
{
static void Main(string[] args)
{
// ファクトリオブジェクトを作成
Creator creatorA = new CreatorA();
Creator creatorB = new CreatorB();
// ファクトリメソッドを使用してプロダクトオブジェクトを作成
IProduct productA = creatorA.FactoryMethod();
IProduct productB = creatorB.FactoryMethod();
// 結果を表示
Console.WriteLine("ProductA says: " + productA.Operation());
Console.WriteLine("ProductB says: " + productB.Operation());
Console.ReadLine();
}
}
このプログラムを実行すると、ProductAとProductBのOperationメソッドが返す結果が表示されます。これにより、ファクトリメソッドパターンを使用してプロダクトインスタンスを正常に作成できたことがわかります。各ファクトリクラスは、どのプロダクトのインスタンスを作成するかを決定します。この方法により、クライアントコードはプロダクトクラスを直接インスタンス化する必要がなく、ファクトリインターフェースにのみ依存するため、プログラムの柔軟性が向上します。
3. 抽象ファクトリパターン(Abstract Factory)
抽象ファクトリパターンは、関連するオブジェクト群(ファミリー)を、その具象クラスを指定せずに作成するためのインターフェースを提供する生成パターンです。このパターンでは、クライアントは抽象インターフェースを通じてクラスを使用するため、クライアントに影響を与えずに実装クラスを交換できます。
以下は、C#での抽象ファクトリパターンのシンプルな実装例です。
// 抽象プロダクト:動物
public interface IAnimal
{
string Speak();
}
// 具象プロダクト:犬
public class Dog : IAnimal
{
public string Speak()
{
return "Bark Bark";
}
}
// 具象プロダクト:猫
public class Cat : IAnimal
{
public string Speak()
{
return "Meow Meow";
}
}
// 抽象ファクトリ
public abstract class IAnimalFactory
{
public abstract IAnimal CreateAnimal();
}
// 具象ファクトリ:犬工場
public class DogFactory : IAnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Dog();
}
}
// 具象ファクトリ:猫工場
public class CatFactory : IAnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Cat();
}
}
上記のコードでは、DogとCatという2つの動物が定義され、どちらもIAnimalインターフェースを実装しています。次に、DogFactoryとCatFactoryという2つのファクトリクラスがあり、これらはIAnimalFactoryを継承しています。DogFactoryはDogを生産し、CatFactoryはCatを生産します。
以下は、これらのファクトリとプロダクトを使用する例です。
class Program
{
static void Main(string[] args)
{
// ファクトリを作成
IAnimalFactory dogFactory = new DogFactory();
IAnimalFactory catFactory = new CatFactory();
// ファクトリを使用してプロダクトを作成
IAnimal dog = dogFactory.CreateAnimal();
IAnimal cat = catFactory.CreateAnimal();
// 結果を表示
Console.WriteLine("Dog says: " + dog.Speak());
Console.WriteLine("Cat says: " + cat.Speak());
Console.ReadLine();
}
}
このプログラムを実行すると、DogとCatのSpeakメソッドの結果が表示され、抽象ファクトリパターンを使用してプロダクトインスタンスを正常に作成できたことがわかります。この方法により、クライアントコードはプロダクトクラスを直接インスタンス化する必要がなく、ファクトリインターフェースにのみ依存するため、プログラムの柔軟性と拡張性が向上します。
4. ビルダーパターン(Builder)
ビルダーパターンは、複雑なオブジェクトの構築プロセスを分離し、同じ構築プロセスで異なる表現を生成できるようにする生成パターンです。
以下は、C#でビルダーパターンを実装したシンプルな例です。
// プロダクト
public class Car
{
public string Engine { get; set; }
public string Wheels { get; set; }
public string Doors { get; set; }
}
// 抽象ビルダー
public abstract class CarBuilder
{
protected Car car;
public void CreateNewCar()
{
car = new Car();
}
public Car GetCar()
{
return car;
}
public abstract void SetEngine();
public abstract void SetWheels();
public abstract void SetDoors();
}
// 具象ビルダー
public class FerrariBuilder : CarBuilder
{
public override void SetEngine()
{
car.Engine = "V8";
}
public override void SetWheels()
{
car.Wheels = "18 inch";
}
public override void SetDoors()
{
car.Doors = "2";
}
}
// ディレクター
public class Director
{
public Car Construct(CarBuilder carBuilder)
{
carBuilder.CreateNewCar();
carBuilder.SetEngine();
carBuilder.SetWheels();
carBuilder.SetDoors();
return carBuilder.GetCar();
}
}
上記のコードでは、Carが作成するプロダクト、CarBuilderが抽象ビルダー(製造手順を定義)、FerrariBuilderが具象ビルダー(各手順を実装)、Directorがディレクター(手順の順序を管理)です。
以下は、このビルダーパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
Director director = new Director();
CarBuilder builder = new FerrariBuilder();
Car ferrari = director.Construct(builder);
Console.WriteLine($"Engine: {ferrari.Engine}, Wheels: {ferrari.Wheels}, Doors: {ferrari.Doors}");
Console.ReadLine();
}
}
このプログラムを実行すると、FerrariBuilderで定義された方法で構築されたCarインスタンスが作成されます。これにより、ビルダーパターンを使用して複雑なオブジェクトの構築プロセスを分離し、同じ構築プロセスで異なる表現を生成できることがわかります。
5. プロトタイプパターン(Prototype)
プロトタイプパターンは、プロトタイプインターフェースを実装して、現在のオブジェクトのクローンを作成する生成パターンです。オブジェクトの直接作成にコストがかかる場合に使用されます。たとえば、高コストなデータベース操作が必要なオブジェクトなどです。
以下は、C#でプロトタイプパターンを実装したシンプルな例です。
// 抽象プロトタイプ
public interface IPrototype
{
IPrototype Clone();
}
// 具象プロトタイプ
public class ConcretePrototype : IPrototype
{
public string Name { get; set; }
public int Value { get; set; }
public IPrototype Clone()
{
// ディープコピーを実装
return (ConcretePrototype)this.MemberwiseClone(); // 具象オブジェクトをクローン
}
}
上記のコードでは、ConcretePrototypeクラスがIPrototypeインターフェースを実装しています。インターフェースはCloneメソッドを定義し、オブジェクトをコピーします。ConcretePrototypeクラスでは、MemberwiseCloneメソッドを使用して新しいクローンオブジェクトを作成します。
以下は、プロトタイプパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
ConcretePrototype prototype = new ConcretePrototype();
prototype.Name = "Original";
prototype.Value = 10;
Console.WriteLine("Original instance: " + prototype.Name + ", " + prototype.Value);
ConcretePrototype clone = (ConcretePrototype)prototype.Clone();
Console.WriteLine("Cloned instance: " + clone.Name + ", " + clone.Value);
Console.ReadLine();
}
}
この例では、ConcretePrototypeオブジェクトを作成してプロパティを設定し、Cloneメソッドを呼び出して新しいConcretePrototypeオブジェクトを作成しています。プログラムを実行すると、元のオブジェクトとクローンオブジェクトのプロパティが同じであることが確認でき、オブジェクトのクローンが正常に行われたことがわかります。
実行フロー:
- 具象プロトタイプオブジェクトを作成し、プロパティを設定。
- プロトタイプの
Cloneメソッドを呼び出して、同じプロパティを持つ新しいオブジェクトを作成。 - 元のオブジェクトとクローンのプロパティを表示して確認。
構造(Structural)パターン
1. アダプターパターン(Adapter)
アダプターパターンの目的は、クラスのインターフェースをクライアントが期待する別のインターフェースに変換することです。アダプターにより、インターフェースの互換性がないために一緒に動作できなかったクラスを連携させることができます。以下はC#でアダプターパターンを実装した例です。
この例では、ITargetインターフェースとAdapteeクラスを作成し、AdapterクラスがITargetを実装してAdapteeのメソッドを使用してITargetの要件を満たします。
// ターゲットインターフェース、クライアントが期待するインターフェース
public interface ITarget
{
string GetRequest();
}
// アダプトされるクラス
public class Adaptee
{
public string GetSpecificRequest()
{
return "Specific request.";
}
}
// アダプタークラス、Adapteeオブジェクトを内部にカプセル化してITargetインターフェースを満たす
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
this._adaptee = adaptee;
}
public string GetRequest()
{
return $"This is '{this._adaptee.GetSpecificRequest()}'";
}
}
// クライアントコード、ITargetインターフェースに準拠するすべてのオブジェクトと互換性がある
public class Client
{
public void MakeRequest(ITarget target)
{
Console.WriteLine(target.GetRequest());
}
}
実行フロー:
class Program
{
static void Main(string[] args)
{
Adaptee adaptee = new Adaptee();
ITarget target = new Adapter(adaptee);
Client client = new Client();
// アダプターによりAdapteeがClientと互換性を持つため、ClientのMakeRequestを呼び出せる
client.MakeRequest(target);
// コンソールがすぐに閉じないようにする
Console.ReadKey();
}
}
上記のコードを実行すると、コンソールに次の出力が表示されます。
This is 'Specific request.'
この例では、AdapterがAdapteeをITargetインターフェースに適合させるクラスです。クライアント(Clientクラス)がAdapterのGetRequestメソッドを呼び出すと、アダプターはそのリクエストをAdapteeのGetSpecificRequestメソッドに転送します。
このように、Adapterクラスを使用することで、本来インターフェースが互換性のないClientクラスとAdapteeクラスが連携できるようになります。
2. ブリッジパターン(Bridge)
ブリッジパターンは、抽象部分と実装部分を分離し、それぞれを独立して変更できるようにする構造パターンです。
以下は、C#でブリッジパターンを実装したシンプルな例です。
// 実装クラスインターフェース
public interface IImplementor
{
void OperationImp();
}
// 具象実装クラスA
public class ConcreteImplementorA : IImplementor
{
public void OperationImp()
{
Console.WriteLine("Concrete Implementor A");
}
}
// 具象実装クラスB
public class ConcreteImplementorB : IImplementor
{
public void OperationImp()
{
Console.WriteLine("Concrete Implementor B");
}
}
// 抽象クラス
public abstract class Abstraction
{
protected IImplementor implementor;
public Abstraction(IImplementor implementor)
{
this.implementor = implementor;
}
public virtual void Operation()
{
implementor.OperationImp();
}
}
// 拡張された抽象クラス
public class RefinedAbstraction : Abstraction
{
public RefinedAbstraction(IImplementor implementor) : base(implementor) { }
public override void Operation()
{
Console.WriteLine("Refined Abstraction is calling implementor's method:");
base.Operation();
}
}
このコードでは、Abstractionが抽象クラスで、IImplementorインターフェースのインスタンスを持ち、そのインスタンスを介して実装クラスのメソッドを呼び出します。RefinedAbstractionは抽象クラスを拡張したものです。ConcreteImplementorAとConcreteImplementorBは実装クラスで、IImplementorインターフェースを実装しています。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
IImplementor implementorA = new ConcreteImplementorA();
Abstraction abstractionA = new RefinedAbstraction(implementorA);
abstractionA.Operation();
IImplementor implementorB = new ConcreteImplementorB();
Abstraction abstractionB = new RefinedAbstraction(implementorB);
abstractionB.Operation();
Console.ReadLine();
}
}
この例では、2つの実装クラスのインスタンスを作成し、それぞれに対応する抽象クラスのインスタンスを作成しています。抽象クラスのOperationメソッドを呼び出すと、実装クラスのOperationImpメソッドが実行されます。
実行フロー:
- 実装クラスのインスタンスを作成。
- 抽象クラスのインスタンスを作成(実装クラスのインスタンスを保持)。
- 抽象クラスの
Operationメソッドを呼び出すと、実装クラスのOperationImpメソッドが実行される。
3. コンポジットパターン(Composite)
コンポジットパターンは、オブジェクトをツリー構造に構成し、個々のオブジェクトと同様に扱えるようにする構造パターンです。このパターンの主な目的は、単一のオブジェクトと複合オブジェクトの一貫性を保つことです。
以下は、C#でコンポジットパターンを実装したシンプルな例です。
// 抽象コンポーネントクラス
public abstract class Component
{
protected string name;
public Component(string name)
{
this.name = name;
}
public abstract void Add(Component c);
public abstract void Remove(Component c);
public abstract void Display(int depth);
}
// リーフ(葉)クラス
public class Leaf : Component
{
public Leaf(string name) : base(name) { }
public override void Add(Component c)
{
Console.WriteLine("Cannot add to a leaf");
}
public override void Remove(Component c)
{
Console.WriteLine("Cannot remove from a leaf");
}
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
}
}
// コンポジット(複合)クラス
public class Composite : Component
{
private List<Component> _children = new List<Component>();
public Composite(string name) : base(name) { }
public override void Add(Component component)
{
_children.Add(component);
}
public override void Remove(Component component)
{
_children.Remove(component);
}
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
// 子ノードを表示
foreach (Component component in _children)
{
component.Display(depth + 2);
}
}
}
このコードでは、Componentが抽象コンポーネントで、名前と追加・削除・表示の操作を定義しています。Leafはリーフ(葉)ノードで、Componentの操作を実装します。Compositeはコンポジット(複合)ノードで、子ノードの追加・削除・表示が可能です。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
Composite root = new Composite("root");
root.Add(new Leaf("Leaf A"));
root.Add(new Leaf("Leaf B"));
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
root.Add(comp);
Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);
root.Add(new Leaf("Leaf C"));
// 追加と削除
Leaf leaf = new Leaf("Leaf D");
root.Add(leaf);
root.Remove(leaf);
// ツリー構造を表示
root.Display(1);
Console.ReadLine();
}
}
この例では、ルートノードを作成し、2つのリーフを追加します。次に、コンポジットノードを作成し、その中に2つのリーフを追加し、ルートに追加します。さらに、別のコンポジットノードを作成して前のコンポジットに追加します。最後に、リーフの追加と削除を行い、ツリー構造を表示します。
実行フロー:
- コンポジットとリーフのオブジェクトを作成。
- コンポジットの
Addメソッドでリーフや他のコンポジットを追加。 - コンポジットの
Removeメソッドでリーフを削除。 Displayメソッドでツリー構造を表示。
4. デコレーターパターン(Decorator)
デコレーターパターンは、実行時に動的にオブジェクトに機能を追加できるようにする構造パターンです。継承よりも柔軟なソリューションを提供します。
以下は、C#でデコレーターパターンを実装したシンプルな例です。
// 抽象コンポーネント
public abstract class Component
{
public abstract string Operation();
}
// 具象コンポーネント
public class ConcreteComponent : Component
{
public override string Operation()
{
return "ConcreteComponent";
}
}
// 抽象デコレーター
public abstract class Decorator : Component
{
protected Component component;
public Decorator(Component component)
{
this.component = component;
}
public override string Operation()
{
if (component != null)
{
return component.Operation();
}
else
{
return string.Empty;
}
}
}
// 具象デコレーターA
public class ConcreteDecoratorA : Decorator
{
public ConcreteDecoratorA(Component comp) : base(comp) { }
public override string Operation()
{
return $"ConcreteDecoratorA({base.Operation()})";
}
}
// 具象デコレーターB
public class ConcreteDecoratorB : Decorator
{
public ConcreteDecoratorB(Component comp) : base(comp) { }
public override string Operation()
{
return $"ConcreteDecoratorB({base.Operation()})";
}
}
このコードでは、Componentが抽象コンポーネントで、Operationメソッドを定義しています。ConcreteComponentは具象コンポーネントで、ComponentのOperationを実装します。Decoratorは抽象デコレーターで、Componentオブジェクトを保持し、Operationをオーバーライドします。ConcreteDecoratorAとConcreteDecoratorBは具象デコレーターで、Decoratorを継承し、Operationをオーバーライドして新しい機能を追加します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
// 基本コンポーネント
Component component = new ConcreteComponent();
Console.WriteLine("Basic Component: " + component.Operation());
// デコレートされたコンポーネント
Component decoratorA = new ConcreteDecoratorA(component);
Console.WriteLine("A Decorated: " + decoratorA.Operation());
Component decoratorB = new ConcreteDecoratorB(decoratorA);
Console.WriteLine("B Decorated: " + decoratorB.Operation());
Console.ReadLine();
}
}
この例では、まずConcreteComponentオブジェクトを作成し、そのOperationを呼び出します。次に、ConcreteDecoratorAでConcreteComponentをデコレートし、そのOperationを呼び出します。最後に、ConcreteDecoratorBでConcreteDecoratorAをデコレートし、そのOperationを呼び出します。これにより、実行時に動的に機能を追加できます。
実行フロー:
- 具象コンポーネントを作成し、その操作を実行。
- デコレーターオブジェクトで具象コンポーネントをラップし、操作を実行(デコレーターは元の操作を呼び出した後に追加処理を実行)。
- 別のデコレーターで前のデコレーターをラップし、操作を実行(同様に連鎖)。
5. ファサードパターン(Facade)
ファサードパターンは、サブシステムの一群のインターフェースに統一的でシンプルなインターフェースを提供する構造パターンです。サブシステムをより使いやすくするための高レベルインターフェースを定義します。
以下は、C#でファサードパターンを実装したシンプルな例です。
// サブシステムA
public class SubSystemA
{
public string OperationA()
{
return "SubSystemA, OperationA\n";
}
}
// サブシステムB
public class SubSystemB
{
public string OperationB()
{
return "SubSystemB, OperationB\n";
}
}
// サブシステムC
public class SubSystemC
{
public string OperationC()
{
return "SubSystemC, OperationC\n";
}
}
// ファサードクラス
public class Facade
{
private SubSystemA a = new SubSystemA();
private SubSystemB b = new SubSystemB();
private SubSystemC c = new SubSystemC();
public string OperationWrapper()
{
string result = "Facade initializes subsystems:\n";
result += a.OperationA();
result += b.OperationB();
result += c.OperationC();
return result;
}
}
このコードでは、SubSystemA、SubSystemB、SubSystemCがサブシステムで、それぞれ操作を持ちます。Facadeはファサードクラスで、サブシステムの操作をカプセル化し、統一インターフェースを提供します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
Facade facade = new Facade();
Console.WriteLine(facade.OperationWrapper());
Console.ReadLine();
}
}
この例では、Facadeオブジェクトを作成し、そのOperationWrapperメソッドを呼び出しています。このメソッドはサブシステムの操作をカプセル化しており、クライアントはサブシステムを直接操作する代わりにファサードを介して操作します。
実行フロー:
- ファサードオブジェクトを作成。
- ファサードのメソッドを呼び出して、間接的にサブシステムを操作。
- サブシステムの操作はファサードのメソッド内でカプセル化されているため、クライアントはサブシステムを直接操作する必要がない。
6. フライウェイトパターン(Flyweight)
フライウェイトパターンは、オブジェクトの作成数を減らしてメモリ使用量を抑え、パフォーマンスを向上させるための構造パターンです。特に多くの類似オブジェクトが必要な場合に有効です。
以下は、C#でフライウェイトパターンを実装したシンプルな例です。
// フライウェイトクラス
public class Flyweight
{
private string intrinsicState;
// コンストラクタ
public Flyweight(string intrinsicState)
{
this.intrinsicState = intrinsicState;
}
// ビジネスメソッド
public void Operation(string extrinsicState)
{
Console.WriteLine($"Intrinsic State = {intrinsicState}, Extrinsic State = {extrinsicState}");
}
}
// フライウェイトファクトリクラス
public class FlyweightFactory
{
private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
public Flyweight GetFlyweight(string key)
{
if (!flyweights.ContainsKey(key))
{
flyweights[key] = new Flyweight(key);
}
return flyweights[key];
}
public int GetFlyweightCount()
{
return flyweights.Count;
}
}
このコードでは、Flyweightがフライウェイトクラスで、不変の内部状態intrinsicStateを持ちます。FlyweightFactoryはフライウェイトファクトリクラスで、フライウェイトオブジェクトのコレクションを管理します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweightA = factory.GetFlyweight("A");
flyweightA.Operation("A operation");
Flyweight flyweightB = factory.GetFlyweight("B");
flyweightB.Operation("B operation");
Flyweight flyweightC = factory.GetFlyweight("A");
flyweightC.Operation("C operation");
Console.WriteLine($"Total Flyweights: {factory.GetFlyweightCount()}");
Console.ReadLine();
}
}
この例では、FlyweightFactoryオブジェクトを作成し、それを使用して2つのフライウェイトオブジェクトを取得しています。3つ目のフライウェイトを取得しようとすると、ファクトリは内部状態が同じであるため、最初のフライウェイトの参照を返します。
実行フロー:
- フライウェイトファクトリを作成。
- ファクトリを介してフライウェイトを取得。すでに存在する場合は既存のオブジェクトを返し、なければ新規作成。
- フライウェイトの操作を実行。
- 現在のフライウェイトの総数を表示。
7. プロキシパターン(Proxy)
プロキシパターンは、別のオブジェクトの代わりとなり、そのオブジェクトへのアクセスを制御する構造パターンです。プロキシはクライアントとターゲットオブジェクトの間で仲介役を果たし、追加機能を提供できます。
以下は、C#でプロキシパターンを実装したシンプルな例です。
// 抽象サブジェクトインターフェース
public interface ISubject
{
void Request();
}
// リアルサブジェクト
public class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("RealSubject: Handling Request.");
}
}
// プロキシ
public class Proxy : ISubject
{
private RealSubject _realSubject;
public Proxy(RealSubject realSubject)
{
this._realSubject = realSubject;
}
public void Request()
{
if (this.CheckAccess())
{
this._realSubject.Request();
this.LogAccess();
}
}
public bool CheckAccess()
{
// アクセス権限をチェック
Console.WriteLine("Proxy: Checking access prior to firing a real request.");
return true;
}
public void LogAccess()
{
// リクエストを記録
Console.WriteLine("Proxy: Logging the time of request.");
}
}
このコードでは、ISubjectがインターフェースで、Requestメソッドを定義しています。RealSubjectはISubjectを実装したクラス、Proxyはプロキシクラスで、ISubjectを実装し、RealSubjectの参照を保持します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Client: Executing the client code with a real subject:");
RealSubject realSubject = new RealSubject();
realSubject.Request();
Console.WriteLine();
Console.WriteLine("Client: Executing the same client code with a proxy:");
Proxy proxy = new Proxy(realSubject);
proxy.Request();
Console.ReadLine();
}
}
この例では、まずRealSubjectのRequestメソッドを直接呼び出し、次にプロキシを介して同じメソッドを呼び出しています。プロキシを介した場合、アクセス権限のチェックやログ記録などの追加処理が実行されます。
実行フロー:
- リアルサブジェクトを作成し、直接
Requestメソッドを呼び出す。 - プロキシオブジェクトを作成(リアルサブジェクトの参照を保持)。
- プロキシを介して
Requestメソッドを呼び出す。プロキシはまずアクセス権限をチェックし、問題がなければリアルサブジェクトのRequestを呼び出し、最後にログを記録する。
振る舞い(Behavioral)パターン
1. 責任の連鎖パターン(Chain of Responsibility)
責任の連鎖パターンは、リクエストを処理するオブジェクトのチェーンを作成する振る舞いパターンです。このパターンでは、リクエストをチェーンに沿って渡し、いずれかのオブジェクトが処理するまで転送します。
この例では、Handler抽象クラスと2つの具象ハンドラConcreteHandler1、ConcreteHandler2を作成します。各ハンドラはリクエストを処理できるかどうかをチェックし、処理できる場合は処理し、できない場合はチェーン内の次のハンドラに渡します。
public abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(int request);
}
public class ConcreteHandler1 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 0 && request < 10)
{
Console.WriteLine($"{this.GetType().Name} handled request {request}");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
public class ConcreteHandler2 : Handler
{
public override void HandleRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine($"{this.GetType().Name} handled request {request}");
}
else if (successor != null)
{
successor.HandleRequest(request);
}
}
}
実行フロー:
class Program
{
static void Main(string[] args)
{
// 責任の連鎖を設定
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
h1.SetSuccessor(h2);
// リクエストを生成して処理
int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };
foreach (int request in requests)
{
h1.HandleRequest(request);
}
Console.ReadKey();
}
}
上記のコードを実行すると、コンソールに次の出力が表示されます。
ConcreteHandler1 handled request 2
ConcreteHandler1 handled request 5
ConcreteHandler2 handled request 14
ConcreteHandler2 handled request 18
ConcreteHandler1 handled request 3
この例では、ConcreteHandler1とConcreteHandler2がハンドラであり、それぞれが受け取ったリクエストを処理できるかどうかを判断します。ConcreteHandler1が処理できない場合、リクエストはConcreteHandler2に渡されます。ConcreteHandler2も処理できない場合、リクエストは無視されます。
この例は、責任の連鎖パターンの核心である「リクエストを処理するオブジェクトのチェーンを作成し、各ハンドラが処理可能か否かを判断してチェーンに沿ってリクエストを渡す」という考え方を示しています。
2. コマンドパターン(Command)
コマンドパターンは、データ駆動型の振る舞いパターンです。リクエストをオブジェクト内にカプセル化し、そのオブジェクトをパラメータとして使用できます。これにより、リクエストのキューイング、ログ、元に戻す操作などをサポートします。
以下は、C#でコマンドパターンを実装したシンプルな例です。
// コマンドインターフェース
public interface ICommand
{
void Execute();
}
// 具象コマンドクラス
public class ConcreteCommand : ICommand
{
private Receiver receiver;
public ConcreteCommand(Receiver receiver)
{
this.receiver = receiver;
}
public void Execute()
{
receiver.Action();
}
}
// レシーバークラス
public class Receiver
{
public void Action()
{
Console.WriteLine("Receiver performs an action");
}
}
// インボーカー(呼び出し元)クラス
public class Invoker
{
private ICommand command;
public void SetCommand(ICommand command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
このコードでは、ICommandがコマンドインターフェースで、Executeメソッドを定義しています。ConcreteCommandは具象コマンドクラスで、ICommandを実装し、Receiverオブジェクトの参照を保持します。Invokerは呼び出し元クラスで、ICommandオブジェクトの参照を保持し、SetCommandでコマンドを設定し、ExecuteCommandでコマンドを実行します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
Receiver receiver = new Receiver();
ICommand command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.SetCommand(command);
invoker.ExecuteCommand();
Console.ReadLine();
}
}
この例では、Receiver、ConcreteCommand、Invokerの各オブジェクトを作成し、InvokerのSetCommandでコマンドを設定し、ExecuteCommandでコマンドを実行しています。
実行フロー:
- レシーバーオブジェクトを作成。
- 具象コマンドオブジェクトを作成し、レシーバーを渡す。
- インボーカーオブジェクトを作成。
- インボーカーの
SetCommandでコマンドを設定。 - インボーカーの
ExecuteCommandでコマンドを実行。
3. インタプリタパターン(Interpreter)
インタプリタパターンは、特定の文法を評価・解釈するための振る舞いパターンです。言語の文法を表現し、解析する方法を定義します。
以下は、C#でインタプリタパターンを実装したシンプルな例です。
// 抽象式
public interface IExpression
{
bool Interpret(string context);
}
// 終端式
public class TerminalExpression : IExpression
{
private string data;
public TerminalExpression(string data)
{
this.data = data;
}
public bool Interpret(string context)
{
if (context.Contains(data))
{
return true;
}
return false;
}
}
// 非終端式(OR)
public class OrExpression : IExpression
{
private IExpression expr1 = null;
private IExpression expr2 = null;
public OrExpression(IExpression expr1, IExpression expr2)
{
this.expr1 = expr1;
this.expr2 = expr2;
}
public bool Interpret(string context)
{
return expr1.Interpret(context) || expr2.Interpret(context);
}
}
このコードでは、IExpressionが抽象式で、Interpretメソッドを定義しています。TerminalExpressionは終端式で、IExpressionを実装します。OrExpressionは非終端式で、同様にIExpressionを実装します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
IExpression isMale = GetMaleExpression();
IExpression isMarriedWoman = GetMarriedWomanExpression();
Console.WriteLine($"John is male? {isMale.Interpret("John")}");
Console.WriteLine($"Julie is a married women? {isMarriedWoman.Interpret("Married Julie")}");
Console.ReadLine();
}
// ルール: Robert と John は男性
public static IExpression GetMaleExpression()
{
IExpression robert = new TerminalExpression("Robert");
IExpression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
// ルール: Julie は既婚女性
public static IExpression GetMarriedWomanExpression()
{
IExpression julie = new TerminalExpression("Julie");
IExpression married = new TerminalExpression("Married");
return new OrExpression(julie, married);
}
}
この例では、「Robert と John は男性」「Julie は既婚女性」という2つのルールを定義し、対応する式オブジェクトを作成して入力文字列を解析しています。
実行フロー:
- 終端式オブジェクトと非終端式オブジェクトを作成し、ルールを表現。
- 式オブジェクトの
Interpretメソッドを呼び出して入力文字列を解析。 - 解析結果を出力。
4. イテレータパターン(Iterator)
イテレータパターンは、オブジェクトの内部表現を公開せずに、その要素に順次アクセスする方法を提供する振る舞いパターンです。以下はC#での実装例です。
// 抽象集合
public interface IAggregate
{
IIterator CreateIterator();
void Add(string item);
int Count { get; }
string this[int index] { get; set; }
}
// 具象集合
public class ConcreteAggregate : IAggregate
{
private List<string> items = new List<string>();
public IIterator CreateIterator()
{
return new ConcreteIterator(this);
}
public int Count
{
get { return items.Count; }
}
public string this[int index]
{
get { return items[index]; }
set { items.Insert(index, value); }
}
public void Add(string item)
{
items.Add(item);
}
}
// 抽象イテレータ
public interface IIterator
{
string First();
string Next();
bool IsDone { get; }
string CurrentItem { get; }
}
// 具象イテレータ
public class ConcreteIterator : IIterator
{
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIterator(ConcreteAggregate aggregate)
{
this.aggregate = aggregate;
}
public string First()
{
return aggregate[0];
}
public string Next()
{
string ret = null;
if (current < aggregate.Count - 1)
{
ret = aggregate[++current];
}
return ret;
}
public string CurrentItem
{
get { return aggregate[current]; }
}
public bool IsDone
{
get { return current >= aggregate.Count; }
}
}
このコードでは、IAggregateが抽象集合で、CreateIteratorなどのメソッドを定義し、ConcreteAggregateが具象集合でIAggregateを実装します。IIteratorが抽象イテレータで、First、Nextなどのメソッドを定義し、ConcreteIteratorが具象イテレータでIIteratorを実装します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
IAggregate aggregate = new ConcreteAggregate();
aggregate.Add("Item A");
aggregate.Add("Item B");
aggregate.Add("Item C");
aggregate.Add("Item D");
IIterator iterator = aggregate.CreateIterator();
Console.WriteLine("Iterating over collection:");
string item = iterator.First();
while (item != null)
{
Console.WriteLine(item);
item = iterator.Next();
}
Console.ReadLine();
}
}
この例では、ConcreteAggregateオブジェクトを作成して要素を追加し、CreateIteratorでイテレータを生成し、イテレータを使用してコレクション内の全要素を走査しています。
実行フロー:
- 集合オブジェクトを作成し、要素を追加。
- 集合の
CreateIteratorメソッドでイテレータを作成。 - イテレータの
Firstで最初の要素を取得し、Nextで次の要素を取得、要素がなくなるまで繰り返す。
5. メディエーターパターン(Mediator)
メディエーターパターンは、オブジェクト間の複雑な通信を減らすための振る舞いパターンです。メディエーター(仲介者)オブジェクトを導入し、オブジェクト同士が直接通信するのではなく、メディエーターを介して通信を行います。
まず、メディエーターインターフェースと具象メディエーターを定義します。
// Mediator インターフェースはコンポーネントとやり取りするメソッドを宣言
public interface IMediator
{
void Notify(object sender, string ev);
}
// 具象 Mediator は協調動作を実装し、複数のコンポーネントを調整
public class ConcreteMediator : IMediator
{
private Component1 _component1;
private Component2 _component2;
public ConcreteMediator(Component1 component1, Component2 component2)
{
_component1 = component1;
_component1.SetMediator(this);
_component2 = component2;
_component2.SetMediator(this);
}
public void Notify(object sender, string ev)
{
if (ev == "A")
{
Console.WriteLine("Mediator reacts on A and triggers following operations:");
this._component2.DoC();
}
if (ev == "D")
{
Console.WriteLine("Mediator reacts on D and triggers following operations:");
this._component1.DoB();
this._component2.DoC();
}
}
}
次に、基本コンポーネントクラスと2つの具象コンポーネントを定義します。
public abstract class BaseComponent
{
protected IMediator _mediator;
public BaseComponent(IMediator mediator = null)
{
_mediator = mediator;
}
public void SetMediator(IMediator mediator)
{
this._mediator = mediator;
}
}
// 具象 Components は各種機能を実装。他のコンポーネントに依存しない。
// また、特定の Mediator クラスにも依存しない。
public class Component1 : BaseComponent
{
public void DoA()
{
Console.WriteLine("Component 1 does A.");
this._mediator.Notify(this, "A");
}
public void DoB()
{
Console.WriteLine("Component 1 does B.");
this._mediator.Notify(this, "B");
}
}
public class Component2 : BaseComponent
{
public void DoC()
{
Console.WriteLine("Component 2 does C.");
this._mediator.Notify(this, "C");
}
public void DoD()
{
Console.WriteLine("Component 2 does D.");
this._mediator.Notify(this, "D");
}
}
最後に、クライアントコードを作成します。
class Program
{
static void Main(string[] args)
{
// クライアントコード
Component1 component1 = new Component1();
Component2 component2 = new Component2();
new ConcreteMediator(component1, component2);
Console.WriteLine("Client triggers operation A.");
component1.DoA();
Console.WriteLine();
Console.WriteLine("Client triggers operation D.");
component2.DoD();
}
}
この例では、各コンポーネントはメディエーターを介して通信します。直接通信しないため、コンポーネント間の依存関係が減り、個別に変更しやすくなります。コンポーネントがイベント(例:「Component 1 does A」)を発生させると、メディエーターが他のコンポーネントに通知し、適切な動作を実行します(例:「Component 2 does C」)。
6. メメントパターン(Memento)
メメントパターンは、オブジェクトの状態を保存し、後で復元できるようにする振る舞いパターンです。オブジェクトのカプセル化を壊さずに状態の保存と復元を可能にします。
以下はシンプルなメメントパターンの実装です。主要なクラスはOriginator(重要な状態を保持し、時間とともに変化)、Memento(Originatorのスナップショットを保持)、Caretaker(Mementoの保存を担当)です。
// Originator クラスはメメントを生成し、メメントから状態を復元できる
public class Originator
{
private string _state;
public Originator(string state)
{
this._state = state;
Console.WriteLine($"Originator: My initial state is: {_state}");
}
public void DoSomething()
{
Console.WriteLine("Originator: I'm doing something important.");
_state = GenerateRandomString(30);
Console.WriteLine($"Originator: and my state has changed to: {_state}");
}
private string GenerateRandomString(int length = 10)
{
string allowedSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
string result = string.Empty;
while (length > 0)
{
result += allowedSymbols[new Random().Next(0, allowedSymbols.Length)];
length--;
}
return result;
}
public IMemento Save()
{
return new ConcreteMemento(_state);
}
public void Restore(IMemento memento)
{
_state = memento.GetState();
Console.WriteLine($"Originator: My state has changed to: {_state}");
}
}
// メメントインターフェースはメメントとOriginatorの状態を取得するメソッドを提供。
// ただし、すべてのメソッドが宣言されているわけではなく、一部はOriginatorのみで宣言。
public interface IMemento
{
string GetName();
string GetState();
DateTime GetDate();
}
// Concrete Memento はOriginatorの状態を保存し、Originatorで復元可能。
// メメントはイミュータブルであるため、setメソッドはない。
public class ConcreteMemento : IMemento
{
private string _state;
private DateTime _date;
public ConcreteMemento(string state)
{
_state = state;
_date = DateTime.Now;
}
public string GetState()
{
return _state;
}
public string GetName()
{
return $"{_date} / ({_state.Substring(0, 9)})...";
}
public DateTime GetDate()
{
return _date;
}
}
// Caretaker は具体的なメメントクラスに依存しない。
// その結果、Originatorの状態にアクセスする権限はなく、メメントのメタデータのみ取得可能。
public class Caretaker
{
private List<IMemento> _mementos = new List<IMemento>();
private Originator _originator = null;
public Caretaker(Originator originator)
{
this._originator = originator;
}
public void Backup()
{
Console.WriteLine("\nCaretaker: Saving Originator's state...");
_mementos.Add(_originator.Save());
}
public void Undo()
{
if (_mementos.Count == 0)
{
return;
}
var memento = _mementos.Last();
_mementos.Remove(memento);
Console.WriteLine("Caretaker: Restoring state to: " + memento.GetName());
try
{
_originator.Restore(memento);
}
catch (Exception)
{
Undo();
}
}
public void ShowHistory()
{
Console.WriteLine("Caretaker: Here's the list of mementos:");
foreach (var memento in _mementos)
{
Console.WriteLine(memento.GetName());
}
}
}
// クライアントコード
class Program
{
static void Main(string[] args)
{
Originator originator = new Originator("Super-duper-super-puper-super.");
Caretaker caretaker = new Caretaker(originator);
caretaker.Backup();
originator.DoSomething();
caretaker.Backup();
originator.DoSomething();
caretaker.Backup();
originator.DoSomething();
Console.WriteLine();
caretaker.ShowHistory();
Console.WriteLine("\nClient: Now, let's rollback!\n");
caretaker.Undo();
Console.WriteLine("\nClient: Once more!\n");
caretaker.Undo();
}
}
上記のコードでは、Originatorが重要な状態を保持し、状態をメメントオブジェクトに保存したり、メメントから復元したりするメソッドを提供します。Caretakerはメメントの保存を担当しますが、メメント内の状態を操作することはできません。ユーザーが操作を実行するたびに現在の状態を保存し、後で新しい状態に不満があれば、以前のメメントから状態を復元できます。
7. オブザーバーパターン(Observer)
オブザーバーパターンは、あるオブジェクトの状態が変化したときに、そのオブジェクトに依存するすべてのオブジェクトが自動的に通知され、更新される振る舞いパターンです。以下はC#での実装例です。
// 抽象オブザーバー
public interface IObserver
{
void Update();
}
// 具象オブザーバー
public class ConcreteObserver : IObserver
{
private string name;
public ConcreteObserver(string name)
{
this.name = name;
}
public void Update()
{
Console.WriteLine($"{name} received an update!");
}
}
// 抽象サブジェクト
public interface ISubject
{
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
void NotifyObservers();
}
// 具象サブジェクト
public class ConcreteSubject : ISubject
{
private List<IObserver> observers = new List<IObserver>();
public void RegisterObserver(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
if (observers.Contains(observer))
{
observers.Remove(observer);
}
}
public void NotifyObservers()
{
foreach (var observer in observers)
{
observer.Update();
}
}
public void ChangeState()
{
// 状態変化をトリガーし、すべてのオブザーバーに通知
NotifyObservers();
}
}
このコードでは、IObserverが抽象オブザーバーで、Updateメソッドを定義し、ConcreteObserverが具象オブザーバーでIObserverを実装します。ISubjectが抽象サブジェクトで、RegisterObserver、RemoveObserver、NotifyObserversメソッドを定義し、ConcreteSubjectが具象サブジェクトでISubjectを実装します。
以下は、このパターンを使用する例です。
class Program
{
static void Main(string[] args)
{
ConcreteSubject subject = new ConcreteSubject();
subject.RegisterObserver(new ConcreteObserver("Observer 1"));
subject.RegisterObserver(new ConcreteObserver("Observer 2"));
subject.RegisterObserver(new ConcreteObserver("Observer 3"));
subject.ChangeState();
Console.ReadLine();
}
}
この例では、ConcreteSubjectオブジェクトを作成し、3つのオブザーバーを登録しています。ChangeStateメソッドを呼び出すと、サブジェクトの状態が変化し、登録されたすべてのオブザーバーに通知が送られます。
実行フロー:
- 具象サブジェクトを作成。
- 具象オブザーバーを複数作成し、サブジェクトの
RegisterObserverで登録。 - サブジェクトの
ChangeStateを呼び出すと、状態変化がトリガーされ、すべての登録オブザーバーに通知される。
8. ステートパターン(State)
ステートパターンは、オブジェクトの内部状態が変化したときにその振る舞いを変更できるようにする振る舞いパターンです。状態を表すクラスを作成し、コンテキストオブジェクトが状態に応じて振る舞いを変えます。
以下はステートパターンの例です。銀行口座を例に、正常状態(NormalState)と引き落とし超過状態(OverdrawnState)を持ちます。ユーザーが預金や引き出しを行うと、口座の状態が変化します。
まず、状態を表すインターフェースを定義します。
public interface IAccountState
{
void Deposit(Action addToBalance);
void Withdraw(Action subtractFromBalance);
void ComputeInterest();
}
次に、2つの具象状態クラスを作成します。
public class NormalState : IAccountState
{
public void Deposit(Action addToBalance)
{
addToBalance();
Console.WriteLine("Deposit in NormalState");
}
public void Withdraw(Action subtractFromBalance)
{
subtractFromBalance();
Console.WriteLine("Withdraw in NormalState");
}
public void ComputeInterest()
{
Console.WriteLine("Interest computed in NormalState");
}
}
public class OverdrawnState : IAccountState
{
public void Deposit(Action addToBalance)
{
addToBalance();
Console.WriteLine("Deposit in OverdrawnState");
}
public void Withdraw(Action subtractFromBalance)
{
Console.WriteLine("No withdraw in OverdrawnState");
}
public void ComputeInterest()
{
Console.WriteLine("Interest and fees computed in OverdrawnState");
}
}
そして、これらの状態を使用するContextクラスを作成します。
public class BankAccount
{
private IAccountState _state;
private double _balance;
public BankAccount(IAccountState state)
{
_state = state;
_balance = 0;
}
public void Deposit(double amount)
{
_state.Deposit(() => _balance += amount);
StateChangeCheck();
}
public void Withdraw(double amount)
{
_state.Withdraw(() => _balance -= amount);
StateChangeCheck();
}
public void ComputeInterest()
{
_state.ComputeInterest();
}
private void StateChangeCheck()
{
if (_balance < 0.0)
_state = new OverdrawnState();
else
_state = new NormalState();
}
}
これで、インスタンスを作成してデモを実行できます。
public class Program
{
public static void Main(string[] args)
{
var account = new BankAccount(new NormalState());
account.Deposit(1000); // Deposit in NormalState
account.Withdraw(2000); // Withdraw in NormalState; No withdraw in OverdrawnState
account.Deposit(100); // Deposit in OverdrawnState
account.ComputeInterest(); // Interest and fees computed in OverdrawnState
Console.ReadKey();
}
}
このプログラムでは、最初は正常状態で預金を行い、残高を超える引き出しを試みると超過状態に移行し、引き出しが拒否されます。しかし、預金は許可され、残高が戻ると正常状態に戻ります。利息計算も状態に応じて動作が変わります。
9. ストラテジーパターン(Strategy)
ストラテジーパターンは、一連のアルゴリズムを定義し、それぞれをカプセル化して交換可能にします。アルゴリズムをクライアントから独立して変更できるようにします。
以下はC#でのシンプルなストラテジーパターンの実装です。ソート戦略(クイックソート、バブルソート)を例にします。
まず、ソート戦略のインターフェースを定義します。
public interface ISortStrategy
{
void Sort(List<int> list);
}
次に、2つの具象戦略クラスを作成します。
public class QuickSort : ISortStrategy
{
public void Sort(List<int> list)
{
list.Sort(); // クイックソート(ビルトインメソッドを使用)
Console.WriteLine("QuickSorted list ");
}
}
public class BubbleSort : ISortStrategy
{
public void Sort(List<int> list)
{
int n = list.Count;
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i - 1; j++)
if (list[j] > list[j + 1])
{
int temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
}
Console.WriteLine("BubbleSorted list ");
}
}
そして、これらの戦略を使用するContextクラスを作成します。
public class SortedList
{
private List<int> _list = new List<int>();
private ISortStrategy _sortstrategy;
public void SetSortStrategy(ISortStrategy sortstrategy)
{
this._sortstrategy = sortstrategy;
}
public void Add(int num)
{
_list.Add(num);
}
public void Sort()
{
_sortstrategy.Sort(_list);
// ソートされたリストを表示
foreach (int num in _list)
{
Console.Write(num + " ");
}
Console.WriteLine();
}
}
これで、インスタンスを作成してデモを実行できます。
public class Program
{
public static void Main(string[] args)
{
SortedList sortedList = new SortedList();
sortedList.Add(1);
sortedList.Add(5);
sortedList.Add(3);
sortedList.Add(4);
sortedList.Add(2);
sortedList.SetSortStrategy(new QuickSort());
sortedList.Sort(); // Output: QuickSorted list 1 2 3 4 5
sortedList.SetSortStrategy(new BubbleSort());
sortedList.Sort(); // Output: BubbleSorted list 1 2 3 4 5
Console.ReadKey();
}
}
このプログラムでは、まず未ソートのリストを作成し、最初にクイックソート戦略でソートし、次にバブルソート戦略でソートしています。
10. テンプレートメソッドパターン(Template Method)
テンプレートメソッドパターンは、アルゴリズムの骨格をメソッド内で定義し、一部のステップをサブクラスに委譲する振る舞いパターンです。サブクラスはアルゴリズムの構造を変更せずに、特定のステップを再定義できます。
以下はテンプレートメソッドパターンの例です。料理の手順を例に、固定ステップ(準備、後片付け)と抽象ステップ(調理)を定義します。
まず、抽象テンプレートクラスを定義します。
public abstract class CookingProcedure
{
// 'Template method'
public void PrepareDish()
{
PrepareIngredients();
Cook();
CleanUp();
}
public void PrepareIngredients()
{
Console.WriteLine("Preparing the ingredients...");
}
// これらのメソッドはサブクラスでオーバーライドされる
public abstract void Cook();
public void CleanUp()
{
Console.WriteLine("Cleaning up...");
}
}
次に、2つの具象サブクラスを作成します。
public class CookPasta : CookingProcedure
{
public override void Cook()
{
Console.WriteLine("Cooking pasta...");
}
}
public class BakeCake : CookingProcedure
{
public override void Cook()
{
Console.WriteLine("Baking cake...");
}
}
これで、デモを実行できます。
public class Program
{
public static void Main(string[] args)
{
CookingProcedure cookingProcedure = new CookPasta();
cookingProcedure.PrepareDish();
Console.WriteLine();
cookingProcedure = new BakeCake();
cookingProcedure.PrepareDish();
Console.ReadKey();
}
}
このプログラムでは、まずCookPastaオブジェクトを作成してPrepareDishを実行し、次にBakeCakeオブジェクトで同様に実行します。両方のオブジェクトは異なるCookメソッドを持ちますが、PrepareDishメソッドの構造(アルゴリズムの骨格)は同じです。
11. ビジターパターン(Visitor)
ビジターパターンは、アルゴリズムをオブジェクト構造から分離する振る舞いパターンです。「ビジター」を使用して要素の操作を変更します。これにより、要素のクラスは要素構造を表現し、具体的な操作はビジタークラスで定義できます。
以下はC#でのビジターパターンの実装例です。主要な部分はビジター(IVisitor)、訪問可能な要素(IElement)、要素構造(ObjectStructure)、具象ビジター(ConcreteVisitor)、具象要素(ConcreteElement)です。
// ビジターインターフェース
public interface IVisitor
{
void VisitConcreteElementA(ConcreteElementA concreteElementA);
void VisitConcreteElementB(ConcreteElementB concreteElementB);
}
// 具象ビジターA
public class ConcreteVisitorA : IVisitor
{
public void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine($"{concreteElementA.GetType().Name} is being visited by {this.GetType().Name}");
}
public void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine($"{concreteElementB.GetType().Name} is being visited by {this.GetType().Name}");
}
}
// 具象ビジターB
public class ConcreteVisitorB : IVisitor
{
public void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine($"{concreteElementA.GetType().Name} is being visited by {this.GetType().Name}");
}
public void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine($"{concreteElementB.GetType().Name} is being visited by {this.GetType().Name}");
}
}
// 要素インターフェース
public interface IElement
{
void Accept(IVisitor visitor);
}
// 具象要素A
public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementA(this);
}
}
// 具象要素B
public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementB(this);
}
}
// オブジェクト構造
public class ObjectStructure
{
private List<IElement> _elements = new List<IElement>();
public void Attach(IElement element)
{
_elements.Add(element);
}
public void Detach(IElement element)
{
_elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (var element in _elements)
{
element.Accept(visitor);
}
}
}
実行フロー:
- 具象要素 ConcreteElementA と ConcreteElementB のインスタンスを作成。
- オブジェクト構造 ObjectStructure のインスタンスを作成し、ステップ1の具象要素を追加。
- 具象ビジター ConcreteVisitorA と ConcreteVisitorB のインスタンスを作成。
- オブジェクト構造の Accept メソッドを呼び出し、ステップ3のビジターを渡す。これにより、ビジターが構造内のすべての要素を訪問する。
以下は、上記のコードを使用する例です。
public class Program
{
public static void Main()
{
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.Attach(new ConcreteElementA());
objectStructure.Attach(new ConcreteElementB());
ConcreteVisitorA visitorA = new ConcreteVisitorA();
ConcreteVisitorB visitorB = new ConcreteVisitorB();
objectStructure.Accept(visitorA);
objectStructure.Accept(visitorB);
Console.ReadKey();
}
}
このプログラムは、ビジターAとビジターBがそれぞれ具象要素Aと具象要素Bを訪問する情報を出力します。
技術交流
.NET Core 交流群:737776595
来自 token 的分享