- 主要な 5 パターン
- 6つのサブパターン
- [1] Singleton
- [2] Factory Method
- [3] Observer
- [4] Strategy
- [5] Decorator
- [6] Abstract Factory
- [7] Builder
- [8] Adapter
- [9] Command
- [10] Proxy
- [11] Composite
- その他 12パターン概要
- 12パターンの詳細
- [12] 1. Prototype(プロトタイプ)
- [13] 2. Bridge(ブリッジ)
- [14] 3. Chain of Responsibility(責任の連鎖)
- [15] 4. Flyweight(フライウェイト)
- [16] 5. Interpreter(インタプリタ)
- [17] 6. Iterator(イテレータ)
- [18] 7. Mediator(メディエータ)
- [19] 8. Memento(メメント)
- [20] 9. State(ステート)
- [21] 10. Template Method(テンプレートメソッド)
- [22] 11. Visitor(ビジター)
- [23] 12. Façade(ファサード)
主要な 5 パターン
C#を用いて、デザインパターンについて、詳しく解説してください。
6つのサブパターン
デザインパターンは、この五つが全てですか。
いいえ、これらは代表的な一部のパターンに過ぎません。GoFのデザインパターンには23種類あり、さらに他の種類のパターンも存在します。
主要な残りのパターンについても、C#での実装例を示させていただきます:
// 1. Abstract Factory パターン
public interface IAbstractFactory
{
IProductA CreateProductA();
IProductB CreateProductB();
}
public interface IProductA
{
string OperationA();
}
public interface IProductB
{
string OperationB();
}
public class ConcreteFactory1 : IAbstractFactory
{
public IProductA CreateProductA() => new ProductA1();
public IProductB CreateProductB() => new ProductB1();
}
public class ProductA1 : IProductA
{
public string OperationA() => "Product A1";
}
public class ProductB1 : IProductB
{
public string OperationB() => "Product B1";
}
// 2. Builder パターン
public class Product
{
public string PartA { get; set; }
public string PartB { get; set; }
public string PartC { get; set; }
}
public class Builder
{
private Product product = new Product();
public Builder BuildPartA()
{
product.PartA = "Part A built";
return this;
}
public Builder BuildPartB()
{
product.PartB = "Part B built";
return this;
}
public Builder BuildPartC()
{
product.PartC = "Part C built";
return this;
}
public Product GetResult()
{
return product;
}
}
// 3. Adapter パターン
public interface ITarget
{
string GetRequest();
}
public class Adaptee
{
public string GetSpecificRequest()
{
return "Specific request.";
}
}
public class Adapter : ITarget
{
private readonly Adaptee _adaptee;
public Adapter(Adaptee adaptee)
{
_adaptee = adaptee;
}
public string GetRequest()
{
return $"Adapted: {_adaptee.GetSpecificRequest()}";
}
}
// 4. Command パターン
public interface ICommand
{
void Execute();
}
public class Receiver
{
public void Action()
{
Console.WriteLine("Receiver: Performing action");
}
}
public class ConcreteCommand : ICommand
{
private readonly Receiver _receiver;
public ConcreteCommand(Receiver receiver)
{
_receiver = receiver;
}
public void Execute()
{
_receiver.Action();
}
}
public class Invoker
{
private ICommand _command;
public void SetCommand(ICommand command)
{
_command = command;
}
public void ExecuteCommand()
{
_command.Execute();
}
}
// 5. Proxy パターン
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 void Request()
{
if (_realSubject == null)
{
_realSubject = new RealSubject();
}
Console.WriteLine("Proxy: Pre-processing request");
_realSubject.Request();
Console.WriteLine("Proxy: Post-processing request");
}
}
// 6. Composite パターン
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 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);
}
}
}
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);
}
}
GoFのデザインパターンは以下の3つのカテゴリーに分類されます:
- 生成に関するパターン
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- 構造に関するパターン
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- 振る舞いに関するパターン
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
それぞれのパターンの主な特徴は:
新しく紹介したパターンの特徴:
- Abstract Factory
- 関連する一連のオブジェクトを作成するインターフェースを提供
- 具体的な実装を隠蔽
- 製品ファミリーの作成に適している
- Builder
- 複雑なオブジェクトの構築過程を分離
- 段階的な構築プロセスを提供
- 同じ構築過程で異なる表現を作成可能
- Adapter
- 互換性のないインターフェース間の変換を実現
- 既存のクラスを再利用可能に
- レガシーコードの統合に有用
- Command
- リクエストをオブジェクトとしてカプセル化
- キューイングや履歴管理が可能
- Undo/Redoの実装に適している
- Proxy
- 他のオブジェクトへのアクセスを制御
- 遅延初期化、アクセス制御、ログ記録などに使用
- 実際のオブジェクトの代理として機能
- Composite
- 部分-全体の階層構造を表現
- 個々のオブジェクトと複合オブジェクトを統一的に扱う
- ツリー構造の実装に適している
これらのパターンは、状況に応じて適切に選択し、組み合わせて使用することが重要です。各パターンには適用すべき状況と避けるべき状況があります。
特定のパターンについて、より詳しい説明や実装例が必要でしょうか?
主要5つとサブ6つの詳細解説
最初の説明の 1.から 5. までと、次の「新しく紹介したパターン」の 1. から 6. までの11パターンについて、一つずつ順番に質問するので、順番に解説してください。
パターンには適用すべき状況と避けるべき状況と、より詳しい説明と実装例をお願いします。
まず、[1] Singleton パターンからお願いします。
[1] Singleton
Singletonパターンについて、詳しく解説させていただきます。
// 基本的なSingletonパターン
public sealed class BasicSingleton
{
private static readonly BasicSingleton instance = new BasicSingleton();
private BasicSingleton() { }
public static BasicSingleton Instance => instance;
}
// スレッドセーフなSingletonパターン(遅延初期化版)
public sealed class ThreadSafeSingleton
{
private static volatile ThreadSafeSingleton instance;
private static readonly object lockObject = new object();
private ThreadSafeSingleton() { }
public static ThreadSafeSingleton Instance
{
get
{
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
}
// より現代的なSingletonパターン(C# 6.0以降)
public sealed class ModernSingleton
{
private ModernSingleton() { }
public static ModernSingleton Instance { get; } = new ModernSingleton();
// シングルトンで管理したい状態やメソッド
private int someState = 0;
public void DoSomething()
{
someState++;
Console.WriteLine($"Current state: {someState}");
}
}
// 実際の使用例:データベース接続管理
public sealed class DatabaseConnection
{
private static readonly DatabaseConnection instance = new DatabaseConnection();
private string connectionString;
private bool isConnected;
private DatabaseConnection()
{
connectionString = "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;";
isConnected = false;
}
public static DatabaseConnection Instance => instance;
public void Connect()
{
if (!isConnected)
{
// 実際のデータベース接続処理
Console.WriteLine($"Connecting to database using: {connectionString}");
isConnected = true;
}
}
public void Disconnect()
{
if (isConnected)
{
// 実際の切断処理
Console.WriteLine("Disconnecting from database");
isConnected = false;
}
}
public void ExecuteQuery(string query)
{
if (!isConnected)
{
Connect();
}
Console.WriteLine($"Executing query: {query}");
}
}
// 使用例
class Program
{
static void Main()
{
// データベース接続の使用例
var db = DatabaseConnection.Instance;
db.Connect();
db.ExecuteQuery("SELECT * FROM Users");
// 別の箇所でも同じインスタンスを使用
var sameDb = DatabaseConnection.Instance;
sameDb.ExecuteQuery("SELECT * FROM Products");
// 同一のインスタンスであることを確認
Console.WriteLine($"Are instances the same? {ReferenceEquals(db, sameDb)}"); // True
db.Disconnect();
}
}
Singletonパターンの詳細解説
目的
- クラスのインスタンスが必ず1つだけ存在することを保証する
- そのインスタンスへのグローバルなアクセスポイントを提供する
適用すべき状況
共有リソースの管理
- データベース接続
- ファイルシステム操作
- プリンタースプーラー
グローバル状態の管理
- アプリケーション設定
- キャッシュ
- ログ記録システム
調整役が必要な場合
- サービスレジストリ
- スレッドプール
- デバイスドライバー
避けるべき状況
状態を持たない純粋なユーティリティクラス
- 静的クラスで十分な場合
複数インスタンスが必要になる可能性がある場合
- テスト時に異なる設定が必要
- 将来的にスケーリングが必要
強い結合を生む可能性がある場合
- ユニットテストが困難になる
- コードの再利用性が低下
実装上の注意点
- スレッドセーフ性の確保
// double-checked lockingパターン
if (instance == null)
{
lock (lockObject)
{
if (instance == null)
{
instance = new ThreadSafeSingleton();
}
}
}
- シリアライゼーション対応
[Serializable]
public sealed class SerializableSingleton
{
private SerializableSingleton() { }
private static readonly SerializableSingleton instance = new SerializableSingleton();
public static SerializableSingleton Instance => instance;
// シリアライゼーション時に同じインスタンスを返すための実装
private Object ReadResolve()
{
return instance;
}
}
- 依存性注入との併用
- Singletonの責務を限定する
- インターフェースを通じて操作する
- テスト時にモック化可能にする
メリット
- インスタンスが1つだけであることを保証
- グローバルアクセスポイントの提供
- 遅延初期化が可能
- インスタンス生成の制御が可能
デメリット
- グローバル状態を導入する
- 単体テストが困難になる可能性
- 密結合を引き起こす可能性
- コードの再利用性が低下する可能性
ベストプラクティス
- privateコンストラクタの使用
private Singleton() { }
- sealed修飾子の使用
public sealed class Singleton
- 静的初期化の利用
private static readonly Singleton instance = new Singleton();
- スレッドセーフな実装の選択
public static Singleton Instance { get; } = new Singleton();
次のパターンについて説明が必要な場合は、お申し付けください。
[2] Factory Method
次に、[2] Factory Method パターンをお願いします。
Factory Methodパターンについて、詳しく解説させていただきます。
// 抽象製品
public interface IDocument
{
void Open();
void Save();
void Close();
}
// 具体的な製品
public class PDFDocument : IDocument
{
public void Open()
{
Console.WriteLine("PDFドキュメントを開きます");
}
public void Save()
{
Console.WriteLine("PDFドキュメントを保存します");
}
public void Close()
{
Console.WriteLine("PDFドキュメントを閉じます");
}
}
public class WordDocument : IDocument
{
public void Open()
{
Console.WriteLine("Wordドキュメントを開きます");
}
public void Save()
{
Console.WriteLine("Wordドキュメントを保存します");
}
public void Close()
{
Console.WriteLine("Wordドキュメントを閉じます");
}
}
// 抽象作成者
public abstract class DocumentCreator
{
// Factory Method
public abstract IDocument CreateDocument();
// テンプレートメソッド
public void OpenDocument()
{
IDocument document = CreateDocument();
document.Open();
// 共通の処理を追加可能
}
}
// 具体的な作成者
public class PDFDocumentCreator : DocumentCreator
{
public override IDocument CreateDocument()
{
return new PDFDocument();
}
}
public class WordDocumentCreator : DocumentCreator
{
public override IDocument CreateDocument()
{
return new WordDocument();
}
}
// より実践的な例:ドキュメント処理システム
public class DocumentProcessor
{
private readonly DocumentCreator documentCreator;
public DocumentProcessor(DocumentCreator creator)
{
documentCreator = creator;
}
public void ProcessDocument()
{
IDocument document = documentCreator.CreateDocument();
document.Open();
// ドキュメント処理
document.Save();
document.Close();
}
}
// 使用例
class Program
{
static void Main()
{
// PDFドキュメントの処理
DocumentCreator pdfCreator = new PDFDocumentCreator();
DocumentProcessor pdfProcessor = new DocumentProcessor(pdfCreator);
pdfProcessor.ProcessDocument();
Console.WriteLine();
// Wordドキュメントの処理
DocumentCreator wordCreator = new WordDocumentCreator();
DocumentProcessor wordProcessor = new DocumentProcessor(wordCreator);
wordProcessor.ProcessDocument();
}
}
// 拡張例:設定に基づくFactory Method
public class DocumentCreatorFactory
{
public static DocumentCreator GetCreator(string documentType)
{
return documentType.ToLower() switch
{
"pdf" => new PDFDocumentCreator(),
"word" => new WordDocumentCreator(),
_ => throw new ArgumentException("未対応のドキュメントタイプです")
};
}
}
Factory Methodパターンの詳細解説
目的
- オブジェクト生成のインターフェースを定義し、具体的な生成をサブクラスに委ねる
- インスタンス化のロジックをカプセル化する
- 生成と使用を分離する
適用すべき状況
クラスが他のクラスのオブジェクトを生成する必要がある場合
- ドキュメント作成システム
- GUIコンポーネントの生成
- データベースコネクターの作成
将来的に生成するオブジェクトの種類が増える可能性がある場合
- プラグインシステム
- フォーマット変換システム
- マルチプラットフォーム対応
オブジェクト生成の条件分岐を整理したい場合
- 設定に基づくオブジェクト生成
- 環境依存のオブジェクト生成
避けるべき状況
生成するオブジェクトの種類が固定で、変更の可能性が低い場合
- 単純なオブジェクト生成で十分な場合
- オーバーエンジニアリングになる可能性
オブジェクト生成のロジックが単純な場合
- 条件分岐が少ない場合
- 単一の生成パターンしかない場合
継承関係が複雑になりすぎる場合
- 継承階層が深くなる場合
- クラス数が多くなりすぎる場合
実装上の注意点
- インターフェースの設計
// 製品のインターフェースは明確で使いやすく
public interface IProduct
{
void DoSomething();
}
// 作成者は拡張しやすく
public abstract class Creator
{
public abstract IProduct CreateProduct();
}
- パラメータ化された Factory Method
public abstract class Creator
{
public abstract IProduct CreateProduct(string productType);
}
- デフォルト実装の提供
public abstract class Creator
{
public virtual IProduct CreateProduct()
{
return new DefaultProduct();
}
}
メリット
- 製品の具体的なクラスから作成コードを分離
- 単一責任原則に従った設計が可能
- オープン/クローズドの原則を満たす
- 柔軟な拡張が可能
デメリット
- 多くのサブクラスを作成する必要がある
- コードが複雑になる可能性がある
- 継承を使用するため、柔軟性に制限がある場合がある
ベストプラクティス
- 明確な命名規則
public class ConcreteProductCreator : Creator
{
public override IProduct CreateProduct()
{
return new ConcreteProduct();
}
}
- エラー処理の考慮
public IProduct CreateProduct(string type)
{
try
{
return type switch
{
"A" => new ProductA(),
"B" => new ProductB(),
_ => throw new ArgumentException($"Unknown product type: {type}")
};
}
catch (Exception ex)
{
// エラーログの記録
throw;
}
}
- 依存性注入との組み合わせ
public class Client
{
private readonly ICreator _creator;
public Client(ICreator creator)
{
_creator = creator;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[3] Observer
[3] Observer パターンをお願いします。
Observerパターンについて、詳しく解説させていただきます。
// イベント引数
public class StockChangeEventArgs : EventArgs
{
public string Symbol { get; }
public decimal Price { get; }
public decimal PreviousPrice { get; }
public StockChangeEventArgs(string symbol, decimal price, decimal previousPrice)
{
Symbol = symbol;
Price = price;
PreviousPrice = previousPrice;
}
}
// Subject(観察対象)インターフェース
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify(StockChangeEventArgs e);
}
// Observer(観察者)インターフェース
public interface IObserver
{
void Update(StockChangeEventArgs e);
}
// 具体的なSubject:株価監視システム
public class StockMarket : ISubject
{
private readonly List<IObserver> _observers = new List<IObserver>();
private readonly Dictionary<string, decimal> _stocks = new Dictionary<string, decimal>();
public void Attach(IObserver observer)
{
if (!_observers.Contains(observer))
{
_observers.Add(observer);
}
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify(StockChangeEventArgs e)
{
foreach (var observer in _observers.ToList()) // ToList()でイテレーション中の変更を許容
{
observer.Update(e);
}
}
public void UpdateStockPrice(string symbol, decimal newPrice)
{
decimal oldPrice = 0;
if (_stocks.ContainsKey(symbol))
{
oldPrice = _stocks[symbol];
}
_stocks[symbol] = newPrice;
Notify(new StockChangeEventArgs(symbol, newPrice, oldPrice));
}
}
// 具体的なObserver:各種監視システム
public class StockDisplay : IObserver
{
private readonly string _name;
public StockDisplay(string name)
{
_name = name;
}
public void Update(StockChangeEventArgs e)
{
var changePercent = (e.Price - e.PreviousPrice) / e.PreviousPrice * 100;
Console.WriteLine($"{_name}: {e.Symbol}の株価が{e.Price:C}に変更されました。" +
$"変動率: {changePercent:F2}%");
}
}
public class AlertSystem : IObserver
{
private const decimal AlertThreshold = 5.0m; // 5%以上の変動で警告
public void Update(StockChangeEventArgs e)
{
if (e.PreviousPrice == 0) return;
var changePercent = Math.Abs((e.Price - e.PreviousPrice) / e.PreviousPrice * 100);
if (changePercent >= AlertThreshold)
{
Console.WriteLine($"警告: {e.Symbol}の株価が大きく変動しました!" +
$"変動率: {changePercent:F2}%");
}
}
}
// イベントベースの実装例
public class ModernStockMarket
{
public event EventHandler<StockChangeEventArgs> StockPriceChanged;
private readonly Dictionary<string, decimal> _stocks = new Dictionary<string, decimal>();
protected virtual void OnStockPriceChanged(StockChangeEventArgs e)
{
StockPriceChanged?.Invoke(this, e);
}
public void UpdateStockPrice(string symbol, decimal newPrice)
{
decimal oldPrice = 0;
if (_stocks.ContainsKey(symbol))
{
oldPrice = _stocks[symbol];
}
_stocks[symbol] = newPrice;
OnStockPriceChanged(new StockChangeEventArgs(symbol, newPrice, oldPrice));
}
}
// 使用例
class Program
{
static void Main()
{
// 従来のObserverパターン
var market = new StockMarket();
var display1 = new StockDisplay("メイン表示");
var display2 = new StockDisplay("サブ表示");
var alertSystem = new AlertSystem();
market.Attach(display1);
market.Attach(display2);
market.Attach(alertSystem);
market.UpdateStockPrice("AAPL", 150.00m);
market.UpdateStockPrice("AAPL", 160.00m);
// イベントベースの実装
var modernMarket = new ModernStockMarket();
modernMarket.StockPriceChanged += (sender, e) =>
{
Console.WriteLine($"イベント: {e.Symbol}の株価が{e.Price:C}に変更されました");
};
modernMarket.UpdateStockPrice("GOOGL", 2800.00m);
modernMarket.UpdateStockPrice("GOOGL", 2900.00m);
}
}
Observerパターンの詳細解説
目的
- オブジェクト間の1対多の依存関係を定義する
- あるオブジェクトの状態が変化した際に、依存するすべてのオブジェクトに自動的に通知し更新する
- 疎結合な設計を実現する
適用すべき状況
状態の変更を複数のオブジェクトに通知する必要がある場合
- GUIイベント処理
- 株価監視システム
- センサーデータの監視
通知を受けるオブジェクトが動的に変更される可能性がある場合
- ユーザー設定可能な通知システム
- プラグイン形式の監視システム
Publisher-Subscriber型の通信が必要な場合
- イベント駆動型システム
- メッセージングシステム
避けるべき状況
単純な1対1の通知で十分な場合
- 直接メソッド呼び出しで対応可能な場合
- イベントチェーンが短い場合
通知の順序が重要な場合
- トランザクション処理
- 厳密な順序制御が必要な場合
オブザーバーの数が多すぎる場合
- パフォーマンスに影響が出る可能性
- メモリ使用量が懸念される場合
実装上の注意点
- メモリリーク対策
public void Detach(IObserver observer)
{
_observers?.Remove(observer);
}
- 非同期通知の考慮
public async Task NotifyAsync(StockChangeEventArgs e)
{
var tasks = _observers.Select(o => Task.Run(() => o.Update(e)));
await Task.WhenAll(tasks);
}
- 例外処理
public void Notify(StockChangeEventArgs e)
{
foreach (var observer in _observers.ToList())
{
try
{
observer.Update(e);
}
catch (Exception ex)
{
// エラーログを記録
// 他のオブザーバーへの通知は継続
}
}
}
メリット
- 疎結合な設計が可能
- 動的なオブジェクト関係の構築
- Open/Closed原則に従った拡張性
- 状態変更の一貫した通知
デメリット
- 通知の順序が保証されない
- メモリリークの可能性
- 予期しない更新連鎖
- デバッグが困難になる可能性
ベストプラクティス
- イベント引数の適切な設計
public class CustomEventArgs : EventArgs
{
public DateTime Timestamp { get; }
public string Data { get; }
public CustomEventArgs(string data)
{
Timestamp = DateTime.UtcNow;
Data = data;
}
}
- 弱参照の使用
private readonly List<WeakReference<IObserver>> _observers = new();
- .NET標準のイベントパターンの活用
public event EventHandler<CustomEventArgs> StateChanged;
protected virtual void OnStateChanged(CustomEventArgs e)
{
StateChanged?.Invoke(this, e);
}
次のパターンについて説明が必要な場合は、お申し付けください。
[4] Strategy
[4] Strategy パターンをお願いします。
Strategyパターンについて、詳しく解説させていただきます。
// 戦略インターフェース
public interface IPaymentStrategy
{
bool Pay(decimal amount);
void ValidatePaymentDetails();
}
// 具体的な戦略クラス群
public class CreditCardPayment : IPaymentStrategy
{
private readonly string _cardNumber;
private readonly string _expiryDate;
private readonly string _cvv;
public CreditCardPayment(string cardNumber, string expiryDate, string cvv)
{
_cardNumber = cardNumber;
_expiryDate = expiryDate;
_cvv = cvv;
}
public bool Pay(decimal amount)
{
ValidatePaymentDetails();
Console.WriteLine($"クレジットカードで{amount:C}を支払いました。");
return true;
}
public void ValidatePaymentDetails()
{
if (string.IsNullOrEmpty(_cardNumber) || _cardNumber.Length != 16)
throw new ArgumentException("無効なカード番号です。");
if (string.IsNullOrEmpty(_expiryDate) || _expiryDate.Length != 5)
throw new ArgumentException("無効な有効期限です。");
if (string.IsNullOrEmpty(_cvv) || _cvv.Length != 3)
throw new ArgumentException("無効なCVV番号です。");
}
}
public class PayPalPayment : IPaymentStrategy
{
private readonly string _email;
private readonly string _password;
public PayPalPayment(string email, string password)
{
_email = email;
_password = password;
}
public bool Pay(decimal amount)
{
ValidatePaymentDetails();
Console.WriteLine($"PayPalで{amount:C}を支払いました。");
return true;
}
public void ValidatePaymentDetails()
{
if (string.IsNullOrEmpty(_email) || !_email.Contains("@"))
throw new ArgumentException("無効なメールアドレスです。");
if (string.IsNullOrEmpty(_password))
throw new ArgumentException("パスワードが必要です。");
}
}
public class BankTransferPayment : IPaymentStrategy
{
private readonly string _accountNumber;
private readonly string _bankCode;
public BankTransferPayment(string accountNumber, string bankCode)
{
_accountNumber = accountNumber;
_bankCode = bankCode;
}
public bool Pay(decimal amount)
{
ValidatePaymentDetails();
Console.WriteLine($"銀行振込で{amount:C}を支払いました。");
return true;
}
public void ValidatePaymentDetails()
{
if (string.IsNullOrEmpty(_accountNumber))
throw new ArgumentException("口座番号が必要です。");
if (string.IsNullOrEmpty(_bankCode))
throw new ArgumentException("銀行コードが必要です。");
}
}
// コンテキストクラス
public class PaymentContext
{
private IPaymentStrategy _paymentStrategy;
private readonly List<string> _paymentHistory = new();
public void SetPaymentStrategy(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public bool ProcessPayment(decimal amount)
{
if (_paymentStrategy == null)
throw new InvalidOperationException("支払い方法が選択されていません。");
bool result = _paymentStrategy.Pay(amount);
if (result)
{
_paymentHistory.Add($"{DateTime.Now}: {amount:C}の支払いが完了しました。");
}
return result;
}
public void ShowPaymentHistory()
{
Console.WriteLine("支払い履歴:");
foreach (var payment in _paymentHistory)
{
Console.WriteLine(payment);
}
}
}
// 戦略ファクトリ
public static class PaymentStrategyFactory
{
public static IPaymentStrategy CreatePaymentStrategy(string paymentMethod, Dictionary<string, string> details)
{
return paymentMethod.ToLower() switch
{
"creditcard" => new CreditCardPayment(
details["cardNumber"],
details["expiryDate"],
details["cvv"]
),
"paypal" => new PayPalPayment(
details["email"],
details["password"]
),
"banktransfer" => new BankTransferPayment(
details["accountNumber"],
details["bankCode"]
),
_ => throw new ArgumentException("未対応の支払い方法です。")
};
}
}
// 使用例
class Program
{
static void Main()
{
var paymentContext = new PaymentContext();
try
{
// クレジットカード支払い
var creditCardDetails = new Dictionary<string, string>
{
{"cardNumber", "1234567890123456"},
{"expiryDate", "12/25"},
{"cvv", "123"}
};
paymentContext.SetPaymentStrategy(
PaymentStrategyFactory.CreatePaymentStrategy("creditcard", creditCardDetails));
paymentContext.ProcessPayment(100.00m);
// PayPal支払い
var paypalDetails = new Dictionary<string, string>
{
{"email", "user@example.com"},
{"password", "password123"}
};
paymentContext.SetPaymentStrategy(
PaymentStrategyFactory.CreatePaymentStrategy("paypal", paypalDetails));
paymentContext.ProcessPayment(50.00m);
// 支払い履歴の表示
paymentContext.ShowPaymentHistory();
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
}
Strategyパターンの詳細解説
目的
- アルゴリズムのファミリーを定義し、それぞれをカプセル化して交換可能にする
- 実行時にアルゴリズムを切り替え可能にする
- クライアントから具体的なアルゴリズムを分離する
適用すべき状況
同じ問題を解決する複数のアルゴリズムが存在する場合
- 支払い処理システム
- ソートアルゴリズム
- 圧縮アルゴリズム
実行時にアルゴリズムを切り替える必要がある場合
- ユーザー設定に基づく処理
- 条件に応じた振る舞いの変更
条件分岐が複雑になる場合の代替として
- 複数のif-else文の置き換え
- switch文の代替
避けるべき状況
アルゴリズムの種類が固定で少ない場合
- 単純な条件分岐で十分な場合
- 変更の可能性が低い場合
アルゴリズムの違いが小さすぎる場合
- オーバーエンジニアリングになる可能性
- 単純なパラメータ変更で対応できる場合
パフォーマンスが重要な場合
- 仮想メソッド呼び出しのオーバーヘッドが問題になる場合
- メモリ使用量が懸念される場合
実装上の注意点
- 戦略の切り替え
public void SetStrategy(IStrategy strategy)
{
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
}
- パラメータの受け渡し
public interface IStrategy
{
Result Execute(Context context, Dictionary<string, object> parameters);
}
- 戦略の検証
public void ValidateStrategy()
{
if (_strategy == null)
throw new InvalidOperationException("戦略が設定されていません。");
}
メリット
- アルゴリズムの交換が容易
- アルゴリズムのカプセル化
- 継承の代替として使用可能
- テストが容易
デメリット
- クラス数の増加
- クライアントが戦略を理解する必要がある
- 戦略間でのデータ共有が困難
- オーバーヘッドの可能性
ベストプラクティス
- デフォルト戦略の設定
public class Context
{
private IStrategy _strategy = new DefaultStrategy();
public void SetStrategy(IStrategy strategy)
{
_strategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
}
}
- 戦略の検証と例外処理
public Result ExecuteStrategy()
{
try
{
ValidateStrategy();
return _strategy.Execute();
}
catch (Exception ex)
{
// エラーログの記録
throw;
}
}
- Factory Patternとの組み合わせ
public static class StrategyFactory
{
public static IStrategy CreateStrategy(string strategyType)
{
return strategyType switch
{
"A" => new StrategyA(),
"B" => new StrategyB(),
_ => throw new ArgumentException($"未対応の戦略タイプ: {strategyType}")
};
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[5] Decorator
[5] Decorator パターンをお願いします。
Decoratorパターンについて、詳しく解説させていただきます。
// コンポーネントのインターフェース
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
// 具象コンポーネント:基本のコーヒー
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return "普通のコーヒー";
}
public decimal GetCost()
{
return 300m;
}
}
// 基本デコレーター
public abstract class CoffeeDecorator : ICoffee
{
protected readonly ICoffee _coffee;
protected CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee ?? throw new ArgumentNullException(nameof(coffee));
}
public virtual string GetDescription()
{
return _coffee.GetDescription();
}
public virtual decimal GetCost()
{
return _coffee.GetCost();
}
}
// 具象デコレーター群
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()} + ミルク";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 50m;
}
}
public class CaramelDecorator : CoffeeDecorator
{
public CaramelDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()} + キャラメル";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 80m;
}
}
public class WhippedCreamDecorator : CoffeeDecorator
{
public WhippedCreamDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return $"{_coffee.GetDescription()} + ホイップクリーム";
}
public override decimal GetCost()
{
return _coffee.GetCost() + 100m;
}
}
// より実践的な例:ロギング機能を持つストリーム処理
public interface IDataSource
{
void WriteData(string data);
string ReadData();
}
public class FileDataSource : IDataSource
{
private readonly string _filename;
public FileDataSource(string filename)
{
_filename = filename;
}
public void WriteData(string data)
{
File.WriteAllText(_filename, data);
}
public string ReadData()
{
return File.ReadAllText(_filename);
}
}
public class DataSourceDecorator : IDataSource
{
protected readonly IDataSource _wrappee;
public DataSourceDecorator(IDataSource source)
{
_wrappee = source;
}
public virtual void WriteData(string data)
{
_wrappee.WriteData(data);
}
public virtual string ReadData()
{
return _wrappee.ReadData();
}
}
public class EncryptionDecorator : DataSourceDecorator
{
public EncryptionDecorator(IDataSource source) : base(source) { }
public override void WriteData(string data)
{
string encrypted = Encrypt(data);
base.WriteData(encrypted);
}
public override string ReadData()
{
string data = base.ReadData();
return Decrypt(data);
}
private string Encrypt(string data)
{
// 実際の暗号化ロジックをここに実装
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(data));
}
private string Decrypt(string data)
{
// 実際の復号化ロジックをここに実装
byte[] bytes = Convert.FromBase64String(data);
return System.Text.Encoding.UTF8.GetString(bytes);
}
}
public class CompressionDecorator : DataSourceDecorator
{
public CompressionDecorator(IDataSource source) : base(source) { }
public override void WriteData(string data)
{
string compressed = Compress(data);
base.WriteData(compressed);
}
public override string ReadData()
{
string data = base.ReadData();
return Decompress(data);
}
private string Compress(string data)
{
// 実際の圧縮ロジックをここに実装
return $"Compressed({data})";
}
private string Decompress(string data)
{
// 実際の解凍ロジックをここに実装
return data.Replace("Compressed(", "").Replace(")", "");
}
}
// 使用例
class Program
{
static void Main()
{
// コーヒーの注文例
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new CaramelDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);
Console.WriteLine($"注文内容: {coffee.GetDescription()}");
Console.WriteLine($"合計金額: {coffee.GetCost():C}");
// ファイル処理の例
IDataSource source = new FileDataSource("test.txt");
source = new EncryptionDecorator(source);
source = new CompressionDecorator(source);
source.WriteData("Hello, World!");
Console.WriteLine(source.ReadData());
}
}
Decoratorパターンの詳細解説
目的
- オブジェクトに動的に新しい責任を追加する
- 継承の代替として柔軟な機能拡張を提供する
- サブクラス化に頼らずにオブジェクトの振る舞いを拡張する
適用すべき状況
動的に機能を追加/削除する必要がある場合
- UIコンポーネントのカスタマイズ
- ストリーム処理の機能追加
- 注文システムのオプション追加
継承が適切でない場合
- 多くの機能の組み合わせが必要
- 実行時の機能変更が必要
- 継承階層が複雑になる場合
既存のコードを変更せずに機能を追加したい場合
- レガシーコードの拡張
- サードパーティライブラリの機能拡張
避けるべき状況
コンポーネントの構造が頻繁に変更される場合
- デコレーターの維持が困難になる
- インターフェースの変更が多い場合
機能の組み合わせが固定的な場合
- 単純な継承で十分な場合
- 機能追加が静的な場合
デコレーターの順序が重要な場合
- 処理順序の依存関係が強い場合
- エラーが起きやすい設計になる場合
実装上の注意点
- インターフェースの一貫性
public interface IComponent
{
void Operation();
string GetState();
}
public class Decorator : IComponent
{
private readonly IComponent _component;
public virtual void Operation()
{
_component.Operation();
}
public virtual string GetState()
{
return _component.GetState();
}
}
- デコレーターの順序管理
public class OrderedDecorator : IComponent
{
private readonly IComponent _component;
private readonly int _order;
public int GetOrder() => _order;
}
- リソース管理
public class ResourceDecorator : IComponent, IDisposable
{
private readonly IComponent _component;
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
if (_component is IDisposable disposable)
{
disposable.Dispose();
}
_disposed = true;
}
}
}
メリット
- 単一責任の原則を満たす
- 実行時の機能追加が可能
- オブジェクトの責任を分離
- 既存コードを変更せずに拡張可能
デメリット
- デコレーターの順序に依存する可能性
- 小さなオブジェクトが多数作成される
- 初期化コードが複雑になる
- デコレーターの削除が困難
ベストプラクティス
- ファクトリーメソッドの使用
public static class ComponentFactory
{
public static IComponent CreateComponent(string[] features)
{
IComponent component = new BaseComponent();
foreach (var feature in features)
{
component = feature switch
{
"logging" => new LoggingDecorator(component),
"caching" => new CachingDecorator(component),
_ => throw new ArgumentException($"Unknown feature: {feature}")
};
}
return component;
}
}
- バリデーション
public class ValidatingDecorator : ComponentDecorator
{
public override void Operation()
{
if (!Validate())
{
throw new InvalidOperationException("Validation failed");
}
base.Operation();
}
private bool Validate()
{
// バリデーションロジック
return true;
}
}
- 透過的な実装
public class TransparentDecorator : IComponent
{
private readonly IComponent _component;
public TransparentDecorator(IComponent component)
{
_component = component;
}
public T GetUnderlyingComponent<T>() where T : IComponent
{
return _component is T component
? component
: (_component as TransparentDecorator)?.GetUnderlyingComponent<T>();
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[6] Abstract Factory
[6] Abstract Factory をお願いします。
Abstract Factoryパターンについて、詳しく解説させていただきます。
// 抽象製品インターフェース
public interface IButton
{
void Render();
void HandleClick();
}
public interface ITextBox
{
void Render();
void HandleInput(string text);
}
public interface ICheckBox
{
void Render();
bool IsChecked { get; set; }
}
// 抽象ファクトリーインターフェース
public interface IUIFactory
{
IButton CreateButton();
ITextBox CreateTextBox();
ICheckBox CreateCheckBox();
string GetThemeName();
}
// Light テーマの具象製品
public class LightButton : IButton
{
public void Render()
{
Console.WriteLine("Light テーマのボタンを描画");
}
public void HandleClick()
{
Console.WriteLine("Light テーマのボタンがクリックされました");
}
}
public class LightTextBox : ITextBox
{
public void Render()
{
Console.WriteLine("Light テーマのテキストボックスを描画");
}
public void HandleInput(string text)
{
Console.WriteLine($"Light テーマのテキストボックスに入力: {text}");
}
}
public class LightCheckBox : ICheckBox
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
_isChecked = value;
Console.WriteLine($"Light テーマのチェックボックス: {(value ? "チェック" : "未チェック")}");
}
}
public void Render()
{
Console.WriteLine($"Light テーマのチェックボックスを描画 (状態: {(_isChecked ? "チェック" : "未チェック")})");
}
}
// Dark テーマの具象製品
public class DarkButton : IButton
{
public void Render()
{
Console.WriteLine("Dark テーマのボタンを描画");
}
public void HandleClick()
{
Console.WriteLine("Dark テーマのボタンがクリックされました");
}
}
public class DarkTextBox : ITextBox
{
public void Render()
{
Console.WriteLine("Dark テーマのテキストボックスを描画");
}
public void HandleInput(string text)
{
Console.WriteLine($"Dark テーマのテキストボックスに入力: {text}");
}
}
public class DarkCheckBox : ICheckBox
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
_isChecked = value;
Console.WriteLine($"Dark テーマのチェックボックス: {(value ? "チェック" : "未チェック")}");
}
}
public void Render()
{
Console.WriteLine($"Dark テーマのチェックボックスを描画 (状態: {(_isChecked ? "チェック" : "未チェック")})");
}
}
// 具象ファクトリー
public class LightUIFactory : IUIFactory
{
public IButton CreateButton() => new LightButton();
public ITextBox CreateTextBox() => new LightTextBox();
public ICheckBox CreateCheckBox() => new LightCheckBox();
public string GetThemeName() => "Light";
}
public class DarkUIFactory : IUIFactory
{
public IButton CreateButton() => new DarkButton();
public ITextBox CreateTextBox() => new DarkTextBox();
public ICheckBox CreateCheckBox() => new DarkCheckBox();
public string GetThemeName() => "Dark";
}
// ファクトリープロバイダー
public static class UIFactoryProvider
{
public static IUIFactory GetFactory(string theme)
{
return theme.ToLower() switch
{
"light" => new LightUIFactory(),
"dark" => new DarkUIFactory(),
_ => throw new ArgumentException($"未対応のテーマです: {theme}")
};
}
}
// アプリケーションクラス
public class Application
{
private readonly IUIFactory _factory;
private IButton _button;
private ITextBox _textBox;
private ICheckBox _checkBox;
public Application(IUIFactory factory)
{
_factory = factory;
CreateUI();
}
private void CreateUI()
{
_button = _factory.CreateButton();
_textBox = _factory.CreateTextBox();
_checkBox = _factory.CreateCheckBox();
}
public void RenderUI()
{
Console.WriteLine($"\n{_factory.GetThemeName()} テーマでUIを描画します");
_button.Render();
_textBox.Render();
_checkBox.Render();
}
public void SimulateUserInteraction()
{
_button.HandleClick();
_textBox.HandleInput("こんにちは、世界!");
_checkBox.IsChecked = true;
}
}
// 使用例
class Program
{
static void Main()
{
// Light テーマでアプリケーションを作成
Console.WriteLine("Light テーマのアプリケーション:");
var lightApp = new Application(UIFactoryProvider.GetFactory("light"));
lightApp.RenderUI();
lightApp.SimulateUserInteraction();
Console.WriteLine("\n-------------------\n");
// Dark テーマでアプリケーションを作成
Console.WriteLine("Dark テーマのアプリケーション:");
var darkApp = new Application(UIFactoryProvider.GetFactory("dark"));
darkApp.RenderUI();
darkApp.SimulateUserInteraction();
}
}
Abstract Factoryパターンの詳細解説
目的
- 関連する製品群を作成するインターフェースを提供する
- 具体的なクラスを指定せずに製品を生成する
- 製品ファミリーの一貫性を保証する
適用すべき状況
関連する製品群を作成する必要がある場合
- UIコンポーネント群
- データベース関連クラス群
- プラットフォーム固有の実装群
製品の一貫性を保証する必要がある場合
- テーマ適用
- クロスプラットフォーム開発
- 環境依存の実装
具体的な実装を隠蔽したい場合
- プラグイン機構
- マルチベンダー対応
- 依存性の逆転
避けるべき状況
製品群が単純で関連性が低い場合
- 単純なFactory Methodで十分な場合
- 製品間の依存関係がない場合
新しい種類の製品を頻繁に追加する必要がある場合
- インターフェース変更が頻繁になる
- 全ファクトリーの更新が必要
製品の種類が多すぎる場合
- インターフェースが肥大化する
- メンテナンスが困難になる
実装上の注意点
- 製品ファミリーの一貫性
public interface IUIFactory
{
string Theme { get; }
bool ValidateComponents(IButton button, ITextBox textBox)
{
return button.GetType().Name.StartsWith(Theme) &&
textBox.GetType().Name.StartsWith(Theme);
}
}
- ファクトリーの初期化
public class FactoryInitializer
{
private readonly IDictionary<string, Func<IUIFactory>> _factories;
public FactoryInitializer()
{
_factories = new Dictionary<string, Func<IUIFactory>>
{
{ "light", () => new LightUIFactory() },
{ "dark", () => new DarkUIFactory() }
};
}
}
- 例外処理
public IUIFactory CreateFactory(string theme)
{
try
{
return theme.ToLower() switch
{
"light" => new LightUIFactory(),
"dark" => new DarkUIFactory(),
_ => throw new ArgumentException($"未対応のテーマ: {theme}")
};
}
catch (Exception ex)
{
// エラーログを記録
throw new FactoryCreationException($"ファクトリーの作成に失敗: {theme}", ex);
}
}
メリット
- 製品の一貫性を保証
- 具象クラスとの結合を防ぐ
- 製品ファミリーの切り替えが容易
- 単一責任原則を満たす
デメリット
- コードが複雑になる
- 新しい種類の製品追加が困難
- 抽象化のレイヤーが増える
- パフォーマンスへの影響
ベストプラクティス
- 依存性注入の活用
public class Client
{
private readonly IUIFactory _factory;
public Client(IUIFactory factory)
{
_factory = factory;
}
}
- ファクトリーの検証
public class ValidatingFactory : IUIFactory
{
private readonly IUIFactory _factory;
public IButton CreateButton()
{
var button = _factory.CreateButton();
if (!ValidateComponent(button))
{
throw new InvalidOperationException("Invalid button created");
}
return button;
}
}
- キャッシュの実装
public class CachingFactory : IUIFactory
{
private readonly IUIFactory _factory;
private readonly Dictionary<Type, object> _cache = new();
public IButton CreateButton()
{
return (IButton)_cache.GetOrAdd(typeof(IButton),
_ => _factory.CreateButton());
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[7] Builder
[7] Builder をお願いします。
Builderパターンについて、詳しく解説させていただきます。
// 複雑な製品クラス
public class Computer
{
public string CPU { get; set; }
public string Motherboard { get; set; }
public string RAM { get; set; }
public string Storage { get; set; }
public string GPU { get; set; }
public string PowerSupply { get; set; }
public string Case { get; set; }
public List<string> USBPorts { get; } = new();
public Dictionary<string, string> Specifications { get; } = new();
public void DisplayConfiguration()
{
Console.WriteLine("コンピュータの構成:");
Console.WriteLine($"CPU: {CPU}");
Console.WriteLine($"マザーボード: {Motherboard}");
Console.WriteLine($"RAM: {RAM}");
Console.WriteLine($"ストレージ: {Storage}");
Console.WriteLine($"GPU: {GPU}");
Console.WriteLine($"電源: {PowerSupply}");
Console.WriteLine($"ケース: {Case}");
Console.WriteLine("USBポート:");
foreach (var port in USBPorts)
{
Console.WriteLine($" - {port}");
}
Console.WriteLine("その他の仕様:");
foreach (var spec in Specifications)
{
Console.WriteLine($" {spec.Key}: {spec.Value}");
}
}
}
// ビルダーインターフェース
public interface IComputerBuilder
{
IComputerBuilder SetCPU(string cpu);
IComputerBuilder SetMotherboard(string motherboard);
IComputerBuilder SetRAM(string ram);
IComputerBuilder SetStorage(string storage);
IComputerBuilder SetGPU(string gpu);
IComputerBuilder SetPowerSupply(string powerSupply);
IComputerBuilder SetCase(string computerCase);
IComputerBuilder AddUSBPort(string portType);
IComputerBuilder AddSpecification(string key, string value);
Computer Build();
}
// 具象ビルダー
public class ComputerBuilder : IComputerBuilder
{
private readonly Computer _computer = new();
public IComputerBuilder SetCPU(string cpu)
{
_computer.CPU = cpu;
return this;
}
public IComputerBuilder SetMotherboard(string motherboard)
{
_computer.Motherboard = motherboard;
return this;
}
public IComputerBuilder SetRAM(string ram)
{
_computer.RAM = ram;
return this;
}
public IComputerBuilder SetStorage(string storage)
{
_computer.Storage = storage;
return this;
}
public IComputerBuilder SetGPU(string gpu)
{
_computer.GPU = gpu;
return this;
}
public IComputerBuilder SetPowerSupply(string powerSupply)
{
_computer.PowerSupply = powerSupply;
return this;
}
public IComputerBuilder SetCase(string computerCase)
{
_computer.Case = computerCase;
return this;
}
public IComputerBuilder AddUSBPort(string portType)
{
_computer.USBPorts.Add(portType);
return this;
}
public IComputerBuilder AddSpecification(string key, string value)
{
_computer.Specifications[key] = value;
return this;
}
public Computer Build()
{
ValidateConfiguration();
return _computer;
}
private void ValidateConfiguration()
{
var missingComponents = new List<string>();
if (string.IsNullOrEmpty(_computer.CPU)) missingComponents.Add("CPU");
if (string.IsNullOrEmpty(_computer.Motherboard)) missingComponents.Add("Motherboard");
if (string.IsNullOrEmpty(_computer.RAM)) missingComponents.Add("RAM");
if (string.IsNullOrEmpty(_computer.PowerSupply)) missingComponents.Add("PowerSupply");
if (missingComponents.Any())
{
throw new InvalidOperationException(
$"必須コンポーネントが不足しています: {string.Join(", ", missingComponents)}");
}
}
}
// ディレクター
public class ComputerDirector
{
private readonly IComputerBuilder _builder;
public ComputerDirector(IComputerBuilder builder)
{
_builder = builder;
}
public Computer BuildGamingComputer()
{
return _builder
.SetCPU("Intel Core i9-12900K")
.SetMotherboard("ASUS ROG Maximus Z690")
.SetRAM("32GB DDR5-6000")
.SetStorage("2TB NVMe SSD")
.SetGPU("NVIDIA RTX 4090")
.SetPowerSupply("1000W 80+ Platinum")
.SetCase("Lian Li PC-O11 Dynamic")
.AddUSBPort("USB 3.2 Type-A")
.AddUSBPort("USB 3.2 Type-C")
.AddSpecification("冷却", "水冷")
.AddSpecification("RGB", "対応")
.Build();
}
public Computer BuildOfficeComputer()
{
return _builder
.SetCPU("Intel Core i5-12400")
.SetMotherboard("ASUS Prime B660")
.SetRAM("16GB DDR4-3200")
.SetStorage("500GB SSD")
.SetGPU("Intel UHD Graphics")
.SetPowerSupply("500W 80+ Bronze")
.SetCase("Fractal Design Define R5")
.AddUSBPort("USB 3.0")
.AddSpecification("冷却", "空冷")
.Build();
}
}
// より実践的な例:設定ファイルからのビルド
public class ConfigurationBuilder : IComputerBuilder
{
private readonly Computer _computer = new();
private readonly IConfiguration _configuration;
public ConfigurationBuilder(IConfiguration configuration)
{
_configuration = configuration;
}
public Computer BuildFromConfiguration()
{
var section = _configuration.GetSection("Computer");
return SetCPU(section["CPU"])
.SetMotherboard(section["Motherboard"])
.SetRAM(section["RAM"])
.SetStorage(section["Storage"])
.SetGPU(section["GPU"])
.SetPowerSupply(section["PowerSupply"])
.SetCase(section["Case"])
.Build();
}
// IComputerBuilder インターフェースの実装(前述のComputerBuilderと同様)
// ...
}
// 使用例
class Program
{
static void Main()
{
var builder = new ComputerBuilder();
var director = new ComputerDirector(builder);
// ゲーミングPCの構築
Console.WriteLine("ゲーミングPC構成:");
var gamingPC = director.BuildGamingComputer();
gamingPC.DisplayConfiguration();
Console.WriteLine("\n-------------------\n");
// オフィスPCの構築
Console.WriteLine("オフィスPC構成:");
var officePC = director.BuildOfficeComputer();
officePC.DisplayConfiguration();
Console.WriteLine("\n-------------------\n");
// カスタムビルド
Console.WriteLine("カスタムPC構成:");
var customPC = new ComputerBuilder()
.SetCPU("AMD Ryzen 9 5950X")
.SetMotherboard("MSI MEG X570 GODLIKE")
.SetRAM("64GB DDR4-3600")
.SetStorage("4TB NVMe SSD")
.SetGPU("AMD Radeon RX 6900 XT")
.SetPowerSupply("1200W 80+ Titanium")
.SetCase("Phanteks Enthoo Elite")
.AddUSBPort("USB 3.2 Gen 2x2 Type-C")
.AddSpecification("カスタム水冷", "デュアルループ")
.Build();
customPC.DisplayConfiguration();
}
}
Builderパターンの詳細解説
目的
- 複雑なオブジェクトの生成プロセスを段階的に行う
- 同じ構築プロセスで異なる表現を作成可能にする
- 構築プロセスと表現を分離する
適用すべき状況
複雑なオブジェクトを作成する必要がある場合
- 多くのパラメータを持つオブジェクト
- 段階的な初期化が必要なオブジェクト
- オプショナルな属性が多いオブジェクト
同じ構築プロセスで異なる表現を作成する場合
- 異なる設定の製品バリエーション
- 環境依存の構成
オブジェクトの構築に特定の順序や規則がある場合
- 依存関係のある初期化
- バリデーションが必要な構築プロセス
避けるべき状況
単純なオブジェクトの生成
- パラメータが少ない場合
- オプショナルな属性が少ない場合
生成プロセスが固定的な場合
- 常に同じ手順で生成される場合
- バリエーションが不要な場合
即時の生成が必要な場合
- パフォーマンスが重要な場合
- 構築プロセスの分割が不要な場合
実装上の注意点
- メソッドチェーンの実装
public IBuilder SetProperty(string value)
{
_product.Property = value;
return this; // thisを返すことでチェーン可能
}
- バリデーション
public Product Build()
{
ValidateRequiredProperties();
ValidateBusinessRules();
return _product;
}
- 不変性の保証
public Product Build()
{
var product = new Product(_property1, _property2);
Reset(); // ビルダーをリセット
return product;
}
メリット
- 段階的な構築が可能
- 同じ構築プロセスで異なる表現を作成可能
- 複雑な構築ロジックを隠蔽
- より細かい構築の制御が可能
デメリット
- コード量が増加
- 製品クラスごとに専用のビルダーが必要
- ディレクターが必要な場合もある
- 単純な場合はオーバーエンジニアリングになる可能性
ベストプラクティス
- デフォルト値の設定
public class Builder
{
private readonly Product _product = new Product
{
Property1 = "default1",
Property2 = "default2"
};
}
- ビルダーのリセット機能
public void Reset()
{
_product = new Product();
SetDefaultValues();
}
private void SetDefaultValues()
{
// デフォルト値の設定
}
- ビルダーの状態検証
private bool IsValid()
{
return !string.IsNullOrEmpty(_product.RequiredProperty1) &&
!string.IsNullOrEmpty(_product.RequiredProperty2);
}
次のパターンについて説明が必要な場合は、お申し付けください。
[8] Adapter
[8] Adapter をお願いします。
Adapterパターンについて、詳しく解説させていただきます。
// 既存のレガシーシステム(Adaptee)
public class LegacyPaymentSystem
{
public void ProcessPaymentLegacy(string accountNumber, double amount)
{
Console.WriteLine($"レガシーシステムで決済処理: 口座番号 {accountNumber}, 金額 {amount:C}");
}
public string GetTransactionHistoryXml()
{
return "<transactions><transaction>...</transaction></transactions>";
}
}
// 新しいシステムで期待されるインターフェース(Target)
public interface IModernPaymentSystem
{
Task<bool> ProcessPaymentAsync(PaymentDetails payment);
Task<List<Transaction>> GetTransactionHistoryAsync();
}
// 新システムで使用される型
public class PaymentDetails
{
public string AccountId { get; set; }
public decimal Amount { get; set; }
public string Currency { get; set; }
public DateTime Timestamp { get; set; }
}
public class Transaction
{
public string Id { get; set; }
public decimal Amount { get; set; }
public DateTime Timestamp { get; set; }
}
// クラスアダプター(継承を使用)
public class PaymentSystemClassAdapter : LegacyPaymentSystem, IModernPaymentSystem
{
public async Task<bool> ProcessPaymentAsync(PaymentDetails payment)
{
try
{
// 通貨変換やその他の必要な処理
double amount = (double)payment.Amount;
ProcessPaymentLegacy(payment.AccountId, amount);
return await Task.FromResult(true);
}
catch (Exception ex)
{
Console.WriteLine($"決済処理エラー: {ex.Message}");
return await Task.FromResult(false);
}
}
public async Task<List<Transaction>> GetTransactionHistoryAsync()
{
var xml = GetTransactionHistoryXml();
// XMLをパースしてTransaction型に変換
var transactions = ParseXmlToTransactions(xml);
return await Task.FromResult(transactions);
}
private List<Transaction> ParseXmlToTransactions(string xml)
{
// XML解析処理(実装例)
return new List<Transaction>
{
new Transaction { Id = "1", Amount = 100m, Timestamp = DateTime.Now }
};
}
}
// オブジェクトアダプター(委譲を使用)
public class PaymentSystemObjectAdapter : IModernPaymentSystem
{
private readonly LegacyPaymentSystem _legacySystem;
public PaymentSystemObjectAdapter(LegacyPaymentSystem legacySystem)
{
_legacySystem = legacySystem ?? throw new ArgumentNullException(nameof(legacySystem));
}
public async Task<bool> ProcessPaymentAsync(PaymentDetails payment)
{
try
{
_legacySystem.ProcessPaymentLegacy(payment.AccountId, (double)payment.Amount);
return await Task.FromResult(true);
}
catch (Exception ex)
{
Console.WriteLine($"決済処理エラー: {ex.Message}");
return await Task.FromResult(false);
}
}
public async Task<List<Transaction>> GetTransactionHistoryAsync()
{
var xml = _legacySystem.GetTransactionHistoryXml();
var transactions = ParseXmlToTransactions(xml);
return await Task.FromResult(transactions);
}
private List<Transaction> ParseXmlToTransactions(string xml)
{
// XML解析処理(実装例)
return new List<Transaction>
{
new Transaction { Id = "1", Amount = 100m, Timestamp = DateTime.Now }
};
}
}
// 双方向アダプター
public class TwoWayPaymentAdapter : IModernPaymentSystem
{
private readonly LegacyPaymentSystem _legacySystem;
private readonly Dictionary<string, string> _modernToLegacyIds;
private readonly Dictionary<string, string> _legacyToModernIds;
public TwoWayPaymentAdapter(LegacyPaymentSystem legacySystem)
{
_legacySystem = legacySystem;
_modernToLegacyIds = new Dictionary<string, string>();
_legacyToModernIds = new Dictionary<string, string>();
}
public async Task<bool> ProcessPaymentAsync(PaymentDetails payment)
{
try
{
// IDのマッピングを保存
var legacyId = GenerateLegacyId(payment.AccountId);
_modernToLegacyIds[payment.AccountId] = legacyId;
_legacyToModernIds[legacyId] = payment.AccountId;
_legacySystem.ProcessPaymentLegacy(legacyId, (double)payment.Amount);
return await Task.FromResult(true);
}
catch (Exception ex)
{
Console.WriteLine($"決済処理エラー: {ex.Message}");
return await Task.FromResult(false);
}
}
public async Task<List<Transaction>> GetTransactionHistoryAsync()
{
var xml = _legacySystem.GetTransactionHistoryXml();
var transactions = ParseXmlToTransactions(xml);
return await Task.FromResult(transactions);
}
private string GenerateLegacyId(string modernId)
{
// モダンIDからレガシーIDへの変換ロジック
return $"LEGACY_{modernId}";
}
}
// キャッシュ機能付きアダプター
public class CachedPaymentAdapter : IModernPaymentSystem
{
private readonly IModernPaymentSystem _adapter;
private readonly Dictionary<string, List<Transaction>> _cache;
private readonly TimeSpan _cacheExpiration;
private DateTime _lastCacheUpdate;
public CachedPaymentAdapter(IModernPaymentSystem adapter, TimeSpan cacheExpiration)
{
_adapter = adapter;
_cache = new Dictionary<string, List<Transaction>>();
_cacheExpiration = cacheExpiration;
}
public async Task<bool> ProcessPaymentAsync(PaymentDetails payment)
{
var result = await _adapter.ProcessPaymentAsync(payment);
if (result)
{
// キャッシュを無効化
_cache.Clear();
}
return result;
}
public async Task<List<Transaction>> GetTransactionHistoryAsync()
{
if (!IsCacheValid())
{
var transactions = await _adapter.GetTransactionHistoryAsync();
UpdateCache(transactions);
return transactions;
}
return _cache["transactions"];
}
private bool IsCacheValid()
{
return _cache.ContainsKey("transactions") &&
DateTime.Now - _lastCacheUpdate < _cacheExpiration;
}
private void UpdateCache(List<Transaction> transactions)
{
_cache["transactions"] = transactions;
_lastCacheUpdate = DateTime.Now;
}
}
// 使用例
class Program
{
static async Task Main()
{
// レガシーシステム
var legacySystem = new LegacyPaymentSystem();
// クラスアダプターの使用
Console.WriteLine("クラスアダプターの使用:");
IModernPaymentSystem classAdapter = new PaymentSystemClassAdapter();
await UsePaymentSystem(classAdapter);
Console.WriteLine("\n-------------------\n");
// オブジェクトアダプターの使用
Console.WriteLine("オブジェクトアダプターの使用:");
IModernPaymentSystem objectAdapter = new PaymentSystemObjectAdapter(legacySystem);
await UsePaymentSystem(objectAdapter);
Console.WriteLine("\n-------------------\n");
// キャッシュ付きアダプターの使用
Console.WriteLine("キャッシュ付きアダプターの使用:");
var cachedAdapter = new CachedPaymentAdapter(objectAdapter, TimeSpan.FromMinutes(5));
await UsePaymentSystem(cachedAdapter);
}
static async Task UsePaymentSystem(IModernPaymentSystem paymentSystem)
{
var payment = new PaymentDetails
{
AccountId = "ACC123",
Amount = 999.99m,
Currency = "JPY",
Timestamp = DateTime.Now
};
var success = await paymentSystem.ProcessPaymentAsync(payment);
Console.WriteLine($"決済処理結果: {success}");
var transactions = await paymentSystem.GetTransactionHistoryAsync();
Console.WriteLine($"取引履歴件数: {transactions.Count}");
}
}
Adapterパターンの詳細解説
目的
- 互換性のないインターフェース間の変換を行う
- 既存のクラスを新しいインターフェースに適合させる
- レガシーコードと新しいコードの統合を可能にする
適用すべき状況
既存のクラスを新しいシステムで使用する場合
- レガシーシステムの統合
- サードパーティライブラリの利用
- 異なるAPIの統合
互換性のないクラス間の連携が必要な場合
- データフォーマットの変換
- プロトコルの変換
- インターフェースの標準化
既存コードの修正が困難または不可能な場合
- ソースコードが利用不可能
- ライセンス上の制約
- 後方互換性の維持
避けるべき状況
単純なインターフェースの違いの場合
- メソッド名の単純な変更
- パラメータの順序変更のみ
大規模な機能の変更が必要な場合
- 完全に異なる機能の実装
- 複雑な変換ロジック
パフォーマンスが重要な場合
- 頻繁に呼び出される処理
- 低レイテンシーが要求される場合
実装上の注意点
- 例外処理
public async Task<Result> AdaptedMethodAsync()
{
try
{
var legacyResult = _legacy.LegacyMethod();
return await ConvertToNewFormat(legacyResult);
}
catch (LegacyException ex)
{
throw new ModernException("変換中にエラーが発生しました", ex);
}
}
- リソース管理
public class ResourceAdapter : IDisposable
{
private readonly LegacyResource _legacy;
public void Dispose()
{
_legacy?.Dispose();
}
}
- スレッドセーフ性
public class ThreadSafeAdapter
{
private readonly object _lock = new object();
public void AdaptedMethod()
{
lock (_lock)
{
// スレッドセーフな処理
}
}
}
メリット
- 既存コードの再利用が可能
- クラス間の結合度を低減
- 段階的なシステム移行が可能
- 責任の分離が明確
デメリット
- 複雑さの増加
- パフォーマンスへの影響
- デバッグの困難さ
- 追加のメンテナンスコスト
ベストプラクティス
- 変換ロジックの分離
public class Converter
{
public static NewType Convert(OldType old)
{
// 変換ロジックを集中管理
return new NewType { /* 変換処理 */ };
}
}
- ファクトリーメソッドの使用
public static IAdapter CreateAdapter(string type)
{
return type switch
{
"legacy" => new LegacyAdapter(),
"modern" => new ModernAdapter(),
_ => throw new ArgumentException($"未対応のタイプ: {type}")
};
}
- キャッシュの実装
public class CachingAdapter
{
private readonly Dictionary<string, object> _cache = new();
public object GetAdaptedResult(string key)
{
if (!_cache.ContainsKey(key))
{
_cache[key] = AdaptResult();
}
return _cache[key];
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[9] Command
[9] Command をお願いします。
Commandパターンについて、詳しく解説させていただきます。
// コマンドインターフェース
public interface ICommand
{
void Execute();
void Undo();
string Description { get; }
}
// テキストエディタの受信者(Receiver)
public class TextEditor
{
private StringBuilder _content = new();
private int _cursorPosition;
public string Content => _content.ToString();
public int CursorPosition => _cursorPosition;
public void InsertText(string text, int position)
{
_content.Insert(position, text);
_cursorPosition = position + text.Length;
}
public void DeleteText(int position, int length)
{
if (position >= 0 && position + length <= _content.Length)
{
_content.Remove(position, length);
_cursorPosition = position;
}
}
public void SetCursorPosition(int position)
{
_cursorPosition = Math.Min(Math.Max(0, position), _content.Length);
}
}
// 具象コマンドクラス群
public class InsertTextCommand : ICommand
{
private readonly TextEditor _editor;
private readonly string _text;
private readonly int _position;
public InsertTextCommand(TextEditor editor, string text, int position)
{
_editor = editor;
_text = text;
_position = position;
}
public void Execute()
{
_editor.InsertText(_text, _position);
}
public void Undo()
{
_editor.DeleteText(_position, _text.Length);
}
public string Description => $"テキスト挿入: '{_text}' (位置: {_position})";
}
public class DeleteTextCommand : ICommand
{
private readonly TextEditor _editor;
private readonly int _position;
private readonly int _length;
private string _deletedText;
public DeleteTextCommand(TextEditor editor, int position, int length)
{
_editor = editor;
_position = position;
_length = length;
}
public void Execute()
{
_deletedText = _editor.Content.Substring(_position, _length);
_editor.DeleteText(_position, _length);
}
public void Undo()
{
_editor.InsertText(_deletedText, _position);
}
public string Description => $"テキスト削除: 位置 {_position} から {_length} 文字";
}
// コマンド履歴管理(Invoker)
public class CommandHistory
{
private readonly Stack<ICommand> _undoStack = new();
private readonly Stack<ICommand> _redoStack = new();
public void ExecuteCommand(ICommand command)
{
try
{
command.Execute();
_undoStack.Push(command);
_redoStack.Clear(); // 新しいコマンドが実行されたらRedoスタックをクリア
}
catch (Exception ex)
{
Console.WriteLine($"コマンド実行エラー: {ex.Message}");
}
}
public void Undo()
{
if (_undoStack.Count > 0)
{
var command = _undoStack.Pop();
try
{
command.Undo();
_redoStack.Push(command);
}
catch (Exception ex)
{
Console.WriteLine($"Undoエラー: {ex.Message}");
_undoStack.Push(command); // エラー時は状態を復元
}
}
}
public void Redo()
{
if (_redoStack.Count > 0)
{
var command = _redoStack.Pop();
try
{
command.Execute();
_undoStack.Push(command);
}
catch (Exception ex)
{
Console.WriteLine($"Redoエラー: {ex.Message}");
_redoStack.Push(command); // エラー時は状態を復元
}
}
}
public void ShowHistory()
{
Console.WriteLine("コマンド履歴:");
foreach (var command in _undoStack.Reverse())
{
Console.WriteLine($"- {command.Description}");
}
}
}
// マクロコマンド(複数のコマンドをグループ化)
public class MacroCommand : ICommand
{
private readonly List<ICommand> _commands = new();
private readonly string _description;
public MacroCommand(string description)
{
_description = description;
}
public void AddCommand(ICommand command)
{
_commands.Add(command);
}
public void Execute()
{
foreach (var command in _commands)
{
command.Execute();
}
}
public void Undo()
{
for (int i = _commands.Count - 1; i >= 0; i--)
{
_commands[i].Undo();
}
}
public string Description => $"マクロ: {_description} ({_commands.Count} コマンド)";
}
// 非同期コマンド
public interface IAsyncCommand
{
Task ExecuteAsync();
Task UndoAsync();
string Description { get; }
}
public class AsyncSaveCommand : IAsyncCommand
{
private readonly TextEditor _editor;
private readonly string _filepath;
private string _previousContent;
public AsyncSaveCommand(TextEditor editor, string filepath)
{
_editor = editor;
_filepath = filepath;
}
public async Task ExecuteAsync()
{
if (File.Exists(_filepath))
{
_previousContent = await File.ReadAllTextAsync(_filepath);
}
await File.WriteAllTextAsync(_filepath, _editor.Content);
}
public async Task UndoAsync()
{
if (_previousContent != null)
{
await File.WriteAllTextAsync(_filepath, _previousContent);
}
else
{
File.Delete(_filepath);
}
}
public string Description => $"ファイル保存: {_filepath}";
}
// 使用例
class Program
{
static async Task Main()
{
var editor = new TextEditor();
var history = new CommandHistory();
// 基本的なテキスト編集
history.ExecuteCommand(new InsertTextCommand(editor, "Hello", 0));
history.ExecuteCommand(new InsertTextCommand(editor, " World!", 5));
Console.WriteLine($"現在のテキスト: {editor.Content}");
// Undo/Redo
history.Undo();
Console.WriteLine($"Undo後: {editor.Content}");
history.Redo();
Console.WriteLine($"Redo後: {editor.Content}");
// マクロコマンド
var macro = new MacroCommand("書式設定");
macro.AddCommand(new DeleteTextCommand(editor, 0, editor.Content.Length));
macro.AddCommand(new InsertTextCommand(editor, "新しいテキスト", 0));
history.ExecuteCommand(macro);
// 履歴表示
history.ShowHistory();
// 非同期コマンド
var saveCommand = new AsyncSaveCommand(editor, "test.txt");
await saveCommand.ExecuteAsync();
}
}
Commandパターンの詳細解説
目的
- リクエストをオブジェクトとしてカプセル化する
- リクエストのパラメータ化、キューイング、ログ記録を可能にする
- Undo/Redo機能を実装可能にする
適用すべき状況
操作の履歴管理が必要な場合
- テキストエディタ
- 画像編集ソフト
- データベース操作
操作のキューイングや遅延実行が必要な場合
- バッチ処理
- スケジュール実行
- トランザクション管理
操作の取り消しや再実行が必要な場合
- Undo/Redo機能
- トランザクションのロールバック
- 状態の復元
避けるべき状況
単純な操作の場合
- 直接メソッド呼び出しで十分な場合
- 履歴管理が不要な場合
メモリ使用量が制限される場合
- コマンドオブジェクトの作成が多い場合
- 履歴保持のコストが高い場合
即時実行のみが必要な場合
- 遅延実行や履歴が不要な場合
- シンプルな処理フローの場合
実装上の注意点
- メモリ管理
public class LimitedHistory
{
private readonly int _maxSize;
private readonly Queue<ICommand> _commands;
public void Add(ICommand command)
{
if (_commands.Count >= _maxSize)
{
_commands.Dequeue();
}
_commands.Enqueue(command);
}
}
- 状態の保存
public class StatePreservingCommand : ICommand
{
private readonly object _state;
public void Execute()
{
SaveState();
// 実行処理
}
private void SaveState()
{
// 状態保存ロジック
}
}
- エラー処理
public void ExecuteWithRetry(ICommand command, int maxRetries)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
command.Execute();
return;
}
catch (Exception ex)
{
if (i == maxRetries - 1) throw;
// リトライ待機
Thread.Sleep(1000 * (i + 1));
}
}
}
メリット
- 単一責任の原則を満たす
- リクエストの履歴管理が容易
- 操作の取り消しと再実行が可能
- 柔軟な実行制御が可能
デメリット
- コード量の増加
- メモリ使用量の増加
- シンプルな操作が複雑化
- 状態管理の複雑さ
ベストプラクティス
- コマンドの軽量化
public class LightweightCommand : ICommand
{
private readonly Action _executeAction;
private readonly Action _undoAction;
public LightweightCommand(Action execute, Action undo)
{
_executeAction = execute;
_undoAction = undo;
}
public void Execute() => _executeAction();
public void Undo() => _undoAction();
}
- コマンドの検証
public class ValidatingCommand : ICommand
{
public void Execute()
{
if (!CanExecute())
{
throw new InvalidOperationException("コマンドを実行できません");
}
// 実行処理
}
protected virtual bool CanExecute()
{
// 検証ロジック
return true;
}
}
- コマンドのロギング
public class LoggingCommandDecorator : ICommand
{
private readonly ICommand _command;
private readonly ILogger _logger;
public void Execute()
{
_logger.Log($"Executing: {_command.Description}");
_command.Execute();
_logger.Log("Execution completed");
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[10] Proxy
[10] Proxy をお願いします。
Proxyパターンについて、詳しく解説させていただきます。
// サービスインターフェース
public interface IImage
{
void Display();
int GetWidth();
int GetHeight();
string GetFileName();
}
// 実際のサービス
public class RealImage : IImage
{
private readonly string _fileName;
private byte[] _imageData;
private int _width;
private int _height;
public RealImage(string fileName)
{
_fileName = fileName;
LoadImageFromDisk();
}
private void LoadImageFromDisk()
{
Console.WriteLine($"画像をロード中: {_fileName}");
// 実際の画像ロード処理をシミュレート
Thread.Sleep(1000);
_imageData = new byte[1024 * 1024]; // 1MB のダミーデータ
_width = 1920;
_height = 1080;
}
public void Display()
{
Console.WriteLine($"画像を表示: {_fileName} ({_width}x{_height})");
}
public int GetWidth() => _width;
public int GetHeight() => _height;
public string GetFileName() => _fileName;
}
// 仮想プロキシ(遅延ロード)
public class LazyLoadingImageProxy : IImage
{
private readonly string _fileName;
private RealImage _realImage;
public LazyLoadingImageProxy(string fileName)
{
_fileName = fileName;
}
public void Display()
{
if (_realImage == null)
{
_realImage = new RealImage(_fileName);
}
_realImage.Display();
}
public int GetWidth()
{
if (_realImage == null)
{
return 0; // メタデータのみを返す
}
return _realImage.GetWidth();
}
public int GetHeight()
{
if (_realImage == null)
{
return 0; // メタデータのみを返す
}
return _realImage.GetHeight();
}
public string GetFileName() => _fileName;
}
// 保護プロキシ(アクセス制御)
public class ProtectionImageProxy : IImage
{
private readonly IImage _realImage;
private readonly string _userRole;
public ProtectionImageProxy(IImage realImage, string userRole)
{
_realImage = realImage;
_userRole = userRole;
}
public void Display()
{
if (!HasViewPermission())
{
throw new UnauthorizedAccessException("画像の表示権限がありません");
}
_realImage.Display();
}
public int GetWidth() => _realImage.GetWidth();
public int GetHeight() => _realImage.GetHeight();
public string GetFileName() => _realImage.GetFileName();
private bool HasViewPermission()
{
return _userRole == "admin" || _userRole == "editor";
}
}
// キャッシュプロキシ
public class CachingImageProxy : IImage
{
private static readonly Dictionary<string, IImage> _imageCache = new();
private readonly string _fileName;
public CachingImageProxy(string fileName)
{
_fileName = fileName;
}
public void Display()
{
if (!_imageCache.ContainsKey(_fileName))
{
Console.WriteLine($"画像をキャッシュに追加: {_fileName}");
_imageCache[_fileName] = new RealImage(_fileName);
}
else
{
Console.WriteLine($"キャッシュから画像を取得: {_fileName}");
}
_imageCache[_fileName].Display();
}
public int GetWidth() => _imageCache.ContainsKey(_fileName) ? _imageCache[_fileName].GetWidth() : 0;
public int GetHeight() => _imageCache.ContainsKey(_fileName) ? _imageCache[_fileName].GetHeight() : 0;
public string GetFileName() => _fileName;
}
// ロギングプロキシ
public class LoggingImageProxy : IImage
{
private readonly IImage _realImage;
private readonly ILogger _logger;
public LoggingImageProxy(IImage realImage, ILogger logger)
{
_realImage = realImage;
_logger = logger;
}
public void Display()
{
_logger.Log($"画像表示開始: {_realImage.GetFileName()}");
var startTime = DateTime.Now;
try
{
_realImage.Display();
var duration = DateTime.Now - startTime;
_logger.Log($"画像表示完了: {_realImage.GetFileName()}, 所要時間: {duration.TotalMilliseconds}ms");
}
catch (Exception ex)
{
_logger.Log($"画像表示エラー: {_realImage.GetFileName()}, エラー: {ex.Message}");
throw;
}
}
public int GetWidth() => _realImage.GetWidth();
public int GetHeight() => _realImage.GetHeight();
public string GetFileName() => _realImage.GetFileName();
}
// ロガーインターフェース
public interface ILogger
{
void Log(string message);
}
// 簡単なロガー実装
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
}
}
// 使用例
class Program
{
static void Main()
{
Console.WriteLine("1. 遅延ロードプロキシのテスト:");
IImage lazyImage = new LazyLoadingImageProxy("large_image.jpg");
Console.WriteLine("プロキシ作成完了(画像はまだロードされていません)");
Console.WriteLine($"ファイル名: {lazyImage.GetFileName()}");
lazyImage.Display(); // この時点で画像がロードされる
Console.WriteLine("\n2. 保護プロキシのテスト:");
IImage protectedImage = new ProtectionImageProxy(
new RealImage("confidential.jpg"), "guest");
try
{
protectedImage.Display();
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"アクセス拒否: {ex.Message}");
}
Console.WriteLine("\n3. キャッシュプロキシのテスト:");
IImage cachedImage = new CachingImageProxy("cached_image.jpg");
cachedImage.Display(); // 初回ロード
cachedImage.Display(); // キャッシュから取得
Console.WriteLine("\n4. ロギングプロキシのテスト:");
IImage loggedImage = new LoggingImageProxy(
new RealImage("logged_image.jpg"),
new ConsoleLogger()
);
loggedImage.Display();
}
}
Proxyパターンの詳細解説
目的
- 他のオブジェクトへのアクセスを制御する
- 追加の機能や制御を提供する
- 実際のオブジェクトの代理として機能する
適用すべき状況
リソースの最適化が必要な場合
- 遅延ロード
- キャッシング
- メモリ使用量の削減
アクセス制御が必要な場合
- セキュリティ制御
- 権限チェック
- ログ記録
リモートリソースへのアクセスを制御する場合
- ネットワーク接続の最適化
- エラー処理
- 接続プーリング
避けるべき状況
シンプルなオブジェクトアクセスの場合
- オーバーヘッドが不要な場合
- 直接アクセスで十分な場合
パフォーマンスが重要な場合
- 低レイテンシーが要求される場合
- プロキシのオーバーヘッドが問題になる場合
システムが既に複雑な場合
- 追加の抽象化層が不要な場合
- メンテナンスコストが増加する場合
実装上の注意点
- 遅延初期化
public class LazyProxy<T> where T : class
{
private T _realObject;
private readonly Func<T> _creator;
public T GetRealObject()
{
if (_realObject == null)
{
lock (this)
{
_realObject ??= _creator();
}
}
return _realObject;
}
}
- リソース解放
public class DisposableProxy : IDisposable
{
private readonly IDisposable _realObject;
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
_realObject?.Dispose();
_disposed = true;
}
}
}
- 例外処理
public class ResilientProxy
{
private readonly int _maxRetries;
private readonly IService _service;
public async Task ExecuteWithRetry()
{
for (int i = 0; i <= _maxRetries; i++)
{
try
{
await _service.ExecuteAsync();
return;
}
catch when (i < _maxRetries)
{
await Task.Delay(1000 * (i + 1));
}
}
}
}
メリット
- セキュリティの向上
- パフォーマンスの最適化
- リソース使用の効率化
- 関心の分離
デメリット
- 複雑さの増加
- レスポンス時間の増加
- 実装の難しさ
- デバッグの困難さ
ベストプラクティス
- プロキシファクトリーの使用
public static class ProxyFactory
{
public static IService CreateProxy(ServiceType type)
{
return type switch
{
ServiceType.Cached => new CachingProxy(),
ServiceType.Protected => new ProtectionProxy(),
ServiceType.Logged => new LoggingProxy(),
_ => throw new ArgumentException()
};
}
}
- プロキシチェーンの実装
public class ProxyChain : IService
{
private readonly List<IProxy> _proxies = new();
public void AddProxy(IProxy proxy)
{
_proxies.Add(proxy);
}
public async Task Execute()
{
foreach (var proxy in _proxies)
{
await proxy.PreExecute();
}
// 実際の処理
for (int i = _proxies.Count - 1; i >= 0; i--)
{
await _proxies[i].PostExecute();
}
}
}
- キャッシュ戦略の実装
public class CacheStrategy
{
private readonly TimeSpan _cacheDuration;
private readonly Dictionary<string, CacheEntry> _cache = new();
public T GetOrCreate<T>(string key, Func<T> factory)
{
if (!_cache.ContainsKey(key) || _cache[key].IsExpired)
{
_cache[key] = new CacheEntry(factory(), DateTime.Now);
}
return (T)_cache[key].Value;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[11] Composite
[11] Composite をお願いします。
Compositeパターンについて、詳しく解説させていただきます。
// コンポーネントの基底クラス
public abstract class FileSystemComponent
{
protected string name;
protected string path;
protected DateTime created;
protected FileSystemComponent(string name)
{
this.name = name;
this.created = DateTime.Now;
}
public abstract long GetSize();
public abstract void Display(int depth = 0);
public abstract void Add(FileSystemComponent component);
public abstract void Remove(FileSystemComponent component);
public abstract IEnumerable<FileSystemComponent> GetChildren();
public abstract bool IsComposite();
public string GetName() => name;
public string GetPath() => path;
public DateTime GetCreated() => created;
protected string GetIndentation(int depth)
{
return new string('-', depth * 2);
}
}
// リーフ(ファイル)
public class File : FileSystemComponent
{
private readonly long size;
public File(string name, long size) : base(name)
{
this.size = size;
}
public override long GetSize() => size;
public override void Display(int depth = 0)
{
Console.WriteLine($"{GetIndentation(depth)}📄 {name} ({FormatSize(size)})");
}
public override void Add(FileSystemComponent component)
{
throw new NotSupportedException("ファイルにコンポーネントを追加できません");
}
public override void Remove(FileSystemComponent component)
{
throw new NotSupportedException("ファイルからコンポーネントを削除できません");
}
public override IEnumerable<FileSystemComponent> GetChildren()
{
return Enumerable.Empty<FileSystemComponent>();
}
public override bool IsComposite() => false;
private string FormatSize(long bytes)
{
string[] units = { "B", "KB", "MB", "GB" };
int unitIndex = 0;
double size = bytes;
while (size >= 1024 && unitIndex < units.Length - 1)
{
size /= 1024;
unitIndex++;
}
return $"{size:F2} {units[unitIndex]}";
}
}
// コンポジット(フォルダ)
public class Folder : FileSystemComponent
{
private readonly List<FileSystemComponent> children = new();
public Folder(string name) : base(name) { }
public override long GetSize()
{
return children.Sum(child => child.GetSize());
}
public override void Display(int depth = 0)
{
Console.WriteLine($"{GetIndentation(depth)}📁 {name} ({FormatSize(GetSize())})");
foreach (var child in children)
{
child.Display(depth + 1);
}
}
public override void Add(FileSystemComponent component)
{
children.Add(component);
}
public override void Remove(FileSystemComponent component)
{
children.Remove(component);
}
public override IEnumerable<FileSystemComponent> GetChildren()
{
return children.AsReadOnly();
}
public override bool IsComposite() => true;
private string FormatSize(long bytes)
{
string[] units = { "B", "KB", "MB", "GB" };
int unitIndex = 0;
double size = bytes;
while (size >= 1024 && unitIndex < units.Length - 1)
{
size /= 1024;
unitIndex++;
}
return $"{size:F2} {units[unitIndex]}";
}
// 検索機能
public IEnumerable<FileSystemComponent> Search(string searchPattern)
{
var results = new List<FileSystemComponent>();
if (name.Contains(searchPattern, StringComparison.OrdinalIgnoreCase))
{
results.Add(this);
}
foreach (var child in children)
{
if (child is File && child.GetName().Contains(searchPattern, StringComparison.OrdinalIgnoreCase))
{
results.Add(child);
}
else if (child is Folder folder)
{
results.AddRange(folder.Search(searchPattern));
}
}
return results;
}
}
// ビジター(追加機能のための)
public interface IFileSystemVisitor
{
void VisitFile(File file);
void VisitFolder(Folder folder);
}
// 具体的なビジター実装
public class SizeAnalyzerVisitor : IFileSystemVisitor
{
private long totalSize = 0;
private int fileCount = 0;
private int folderCount = 0;
public void VisitFile(File file)
{
totalSize += file.GetSize();
fileCount++;
}
public void VisitFolder(Folder folder)
{
folderCount++;
foreach (var child in folder.GetChildren())
{
if (child is File file)
{
VisitFile(file);
}
else if (child is Folder childFolder)
{
VisitFolder(childFolder);
}
}
}
public void DisplayStatistics()
{
Console.WriteLine("\n統計情報:");
Console.WriteLine($"総サイズ: {FormatSize(totalSize)}");
Console.WriteLine($"ファイル数: {fileCount}");
Console.WriteLine($"フォルダ数: {folderCount}");
}
private string FormatSize(long bytes)
{
string[] units = { "B", "KB", "MB", "GB" };
int unitIndex = 0;
double size = bytes;
while (size >= 1024 && unitIndex < units.Length - 1)
{
size /= 1024;
unitIndex++;
}
return $"{size:F2} {units[unitIndex]}";
}
}
// 使用例
class Program
{
static void Main()
{
// ファイルシステム構造の作成
var root = new Folder("Root");
var documents = new Folder("Documents");
documents.Add(new File("resume.doc", 1024 * 1024)); // 1MB
documents.Add(new File("photo.jpg", 2 * 1024 * 1024)); // 2MB
var music = new Folder("Music");
var jazz = new Folder("Jazz");
jazz.Add(new File("song1.mp3", 5 * 1024 * 1024)); // 5MB
jazz.Add(new File("song2.mp3", 6 * 1024 * 1024)); // 6MB
music.Add(jazz);
root.Add(documents);
root.Add(music);
root.Add(new File("config.sys", 512)); // 512B
// ファイルシステムの表示
Console.WriteLine("ファイルシステム構造:");
root.Display();
// 検索機能の使用
Console.WriteLine("\n'.mp3'を含むファイルの検索結果:");
var searchResults = root.Search(".mp3");
foreach (var result in searchResults)
{
result.Display();
}
// 統計情報の収集
var analyzer = new SizeAnalyzerVisitor();
analyzer.VisitFolder(root);
analyzer.DisplayStatistics();
}
}
Compositeパターンの詳細解説
目的
- 部分-全体の階層構造を表現する
- クライアントが個々のオブジェクトと複合オブジェクトを統一的に扱えるようにする
- 再帰的な構造を効率的に処理する
適用すべき状況
階層構造を表現する必要がある場合
- ファイルシステム
- 組織構造
- GUIコンポーネント
再帰的な処理が必要な場合
- ツリー構造の探索
- 集計計算
- 深さ優先処理
統一的なインターフェースが必要な場合
- 共通操作の実装
- クライアントコードの簡素化
- 一貫した処理方法
避けるべき状況
階層が浅い場合
- フラットな構造の場合
- 単純なコレクション
型の安全性が重要な場合
- 厳格な型チェックが必要な場合
- 異なる振る舞いが必要な場合
パフォーマンスが重要な場合
- 大量のオブジェクト生成
- 深い再帰が必要な場合
実装上の注意点
- 子要素の管理
protected readonly List<Component> children = new();
public virtual void Add(Component component)
{
if (component == null)
throw new ArgumentNullException(nameof(component));
children.Add(component);
}
- 再帰的な処理
public virtual void Process()
{
// 自身の処理
ProcessSelf();
// 子要素の処理
foreach (var child in children)
{
child.Process();
}
}
- 循環参照の防止
public void Add(Component component)
{
if (IsAncestor(component))
throw new InvalidOperationException("循環参照は許可されていません");
children.Add(component);
}
private bool IsAncestor(Component component)
{
var current = this;
while (current != null)
{
if (current == component)
return true;
current = current.Parent;
}
return false;
}
メリット
- 階層構造の簡単な表現
- クライアントコードの単純化
- 新しい要素タイプの追加が容易
- 再帰的な処理の自然な実装
デメリット
- 設計が抽象的になりすぎる可能性
- 制約の実装が困難
- パフォーマンスへの影響
- コンポーネント間の区別が難しい
ベストプラクティス
- 親参照の管理
public class Component
{
protected Component parent;
public Component Parent
{
get => parent;
private set
{
if (parent != value)
{
parent?.RemoveChild(this);
parent = value;
parent?.AddChild(this);
}
}
}
}
- イテレータの実装
public IEnumerator<Component> GetEnumerator()
{
yield return this;
foreach (var child in children)
{
foreach (var component in child)
{
yield return component;
}
}
}
- ビジターパターンの統合
public interface IVisitor
{
void Visit(Leaf leaf);
void Visit(Composite composite);
}
public abstract class Component
{
public abstract void Accept(IVisitor visitor);
}
次のパターンについて説明が必要な場合は、お申し付けください。
その他の 12パターン
その他 12パターン概要
GoFの23パターンの内、 詳細を解説してもらった11パターン以外の、
残り12パターンについて、簡単に解説してぐたさい。
GoFの残り12パターンについて、簡潔に解説いたします:
- Prototype(プロトタイプ)
- 目的:既存のオブジェクトをクローンして新しいオブジェクトを作成する
- 用途:オブジェクトの作成コストが高い場合や、動的にインスタンスを生成する必要がある場合
- 例:ドキュメントのコピー、ゲームオブジェクトの複製
- Bridge(ブリッジ)
- 目的:抽象部分と実装部分を分離し、それぞれを独立して変更可能にする
- 用途:プラットフォーム固有の実装を抽象化する場合
- 例:クロスプラットフォームGUI、デバイスドライバー
- Chain of Responsibility(責任の連鎖)
- 目的:複数のオブジェクトがリクエストを処理する機会を与える
- 用途:イベント処理、エラーハンドリング
- 例:例外処理チェーン、ミドルウェアパイプライン
- Flyweight(フライウェイト)
- 目的:多数の同様なオブジェクトを共有することでメモリ使用を最適化
- 用途:大量の類似オブジェクトを扱う場合
- 例:文字フォント、ゲームの粒子システム
- Interpreter(インタプリタ)
- 目的:文法規則を表現し、それを解釈する
- 用途:DSL(ドメイン固有言語)の実装
- 例:正規表現エンジン、数式パーサー
- Iterator(イテレータ)
- 目的:コレクションの要素に順次アクセスする方法を提供
- 用途:データ構造の走査
- 例:foreach文の実装、データベースクエリ結果の処理
- Mediator(メディエータ)
- 目的:オブジェクト間の相互作用を集中管理
- 用途:複雑な依存関係の管理
- 例:航空管制システム、チャットルーム
- Memento(メメント)
- 目的:オブジェクトの状態を保存・復元する
- 用途:Undo/Redo機能、チェックポイント
- 例:テキストエディタ、ゲームのセーブ機能
- State(ステート)
- 目的:オブジェクトの状態に応じて振る舞いを変更
- 用途:状態遷移の管理
- 例:自動販売機の制御、ワークフロー管理
- Template Method(テンプレートメソッド)
- 目的:アルゴリズムの骨格を定義し、一部の手順をサブクラスで実装
- 用途:共通処理のフレームワーク化
- 例:データベース接続処理、アプリケーションのライフサイクル
- Visitor(ビジター)
- 目的:データ構造とそれに対する操作を分離
- 用途:複雑なデータ構造に対する新しい操作の追加
- 例:AST(抽象構文木)の処理、レポート生成
- Façade(ファサード)
- 目的:複雑なサブシステムに対する単純なインターフェースを提供
- 用途:サブシステムの使用を簡単にする
- 例:ライブラリのラッパー、APIの統合
これらのパターンも状況に応じて有用ですが、オーバーエンジニアリングを避けるため、必要な場合にのみ適用することが重要です。特定のパターンについて、より詳しい説明が必要な場合はお申し付けください。
12パターンの詳細
この12パターンのより詳しい説明をお願いします。 C#のサンプルコードもお願いします。
承知しました。12パターンを順番に詳しく解説していきます。 1つずつ説明させていただきますので、まずどのパターンから解説しましょうか?
- Prototype(プロトタイプ)
- Bridge(ブリッジ)
- Chain of Responsibility(責任の連鎖)
- Flyweight(フライウェイト)
- Interpreter(インタプリタ)
- Iterator(イテレータ)
- Mediator(メディエータ)
- Memento(メメント)
- State(ステート)
- Template Method(テンプレートメソッド)
- Visitor(ビジター)
- Façade(ファサード)
最初にどのパターンについて説明しましょうか?
[12] 1. Prototype(プロトタイプ)
[12] 1. Prototype(プロトタイプ)ついて説明してください。
Prototypeパターンについて、詳しく解説させていただきます。
// プロトタイプのインターフェース
public interface IPrototype<T>
{
T Clone();
}
// 複雑なオブジェクト
public class Document : IPrototype<Document>
{
public string Title { get; set; }
public string Content { get; set; }
public List<string> Tags { get; set; }
public DocumentMetadata Metadata { get; set; }
public Dictionary<string, string> Properties { get; set; }
public Document()
{
Tags = new List<string>();
Properties = new Dictionary<string, string>();
Metadata = new DocumentMetadata();
}
// ディープコピーの実装
public Document Clone()
{
var clone = new Document
{
Title = this.Title,
Content = this.Content,
Tags = new List<string>(this.Tags),
Properties = new Dictionary<string, string>(this.Properties),
Metadata = this.Metadata.Clone()
};
return clone;
}
}
public class DocumentMetadata : IPrototype<DocumentMetadata>
{
public DateTime Created { get; set; }
public string Author { get; set; }
public int Version { get; set; }
public DocumentMetadata Clone()
{
return new DocumentMetadata
{
Created = this.Created,
Author = this.Author,
Version = this.Version
};
}
}
// プロトタイプレジストリ(プロトタイプの管理)
public class DocumentPrototypeRegistry
{
private readonly Dictionary<string, Document> _prototypes = new();
public void RegisterPrototype(string key, Document prototype)
{
_prototypes[key] = prototype;
}
public Document CreateDocument(string key)
{
if (!_prototypes.ContainsKey(key))
{
throw new ArgumentException($"プロトタイプが見つかりません: {key}");
}
return _prototypes[key].Clone();
}
public IEnumerable<string> GetAvailablePrototypes()
{
return _prototypes.Keys;
}
}
// シリアライゼーションを使用したディープコピーの実装例
public class SerializableDocument : IPrototype<SerializableDocument>
{
public string Title { get; set; }
public string Content { get; set; }
public List<string> Tags { get; set; }
public SerializableDocument Clone()
{
// シリアライゼーションを使用したディープコピー
using var stream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return (SerializableDocument)formatter.Deserialize(stream);
}
}
// プロトタイプファクトリ
public class DocumentFactory
{
private readonly DocumentPrototypeRegistry _registry;
public DocumentFactory()
{
_registry = new DocumentPrototypeRegistry();
InitializePrototypes();
}
private void InitializePrototypes()
{
// 標準的なドキュメントテンプレート
var standardDoc = new Document
{
Title = "標準ドキュメント",
Metadata = new DocumentMetadata
{
Author = "システム",
Created = DateTime.Now,
Version = 1
}
};
standardDoc.Properties["Type"] = "Standard";
// レポートテンプレート
var reportDoc = new Document
{
Title = "レポート",
Metadata = new DocumentMetadata
{
Author = "システム",
Created = DateTime.Now,
Version = 1
}
};
reportDoc.Properties["Type"] = "Report";
reportDoc.Tags.Add("report");
_registry.RegisterPrototype("standard", standardDoc);
_registry.RegisterPrototype("report", reportDoc);
}
public Document CreateDocument(string type)
{
return _registry.CreateDocument(type);
}
}
// 使用例
class Program
{
static void Main()
{
var factory = new DocumentFactory();
// 標準ドキュメントの作成
var doc1 = factory.CreateDocument("standard");
doc1.Title = "私の文書";
doc1.Content = "これは文書の内容です。";
doc1.Tags.Add("personal");
// レポートの作成
var doc2 = factory.CreateDocument("report");
doc2.Title = "月次レポート";
doc2.Content = "これは月次レポートです。";
doc2.Tags.Add("monthly");
// ドキュメントの内容を表示
DisplayDocument("ドキュメント1:", doc1);
Console.WriteLine();
DisplayDocument("ドキュメント2:", doc2);
}
static void DisplayDocument(string header, Document doc)
{
Console.WriteLine(header);
Console.WriteLine($"タイトル: {doc.Title}");
Console.WriteLine($"内容: {doc.Content}");
Console.WriteLine($"タグ: {string.Join(", ", doc.Tags)}");
Console.WriteLine($"作成者: {doc.Metadata.Author}");
Console.WriteLine($"バージョン: {doc.Metadata.Version}");
Console.WriteLine("プロパティ:");
foreach (var prop in doc.Properties)
{
Console.WriteLine($" {prop.Key}: {prop.Value}");
}
}
}
Prototypeパターンの詳細解説
目的
- 既存のオブジェクトをクローンして新しいオブジェクトを作成する
- オブジェクトの作成コストを削減する
- 実行時にオブジェクトを動的に生成する
適用すべき状況
オブジェクトの作成コストが高い場合
- 複雑なオブジェクトの初期化
- データベースからのロード
- 外部リソースの取得
同様のオブジェクトを多数生成する必要がある場合
- テンプレートからの文書作成
- ゲームオブジェクトの複製
- 設定のコピー
オブジェクトの状態をコピーする必要がある場合
- スナップショットの作成
- バックアップ
- 状態の複製
避けるべき状況
オブジェクトが単純な場合
- 単純なコンストラクタで十分な場合
- 状態を持たないオブジェクト
一意性が重要な場合
- IDや一意の識別子が必要な場合
- シングルトンパターンが適切な場合
深いコピーが困難な場合
- 循環参照がある場合
- 外部リソースへの参照を持つ場合
実装上の注意点
- シャローコピーとディープコピー
// シャローコピー
public object Clone()
{
return this.MemberwiseClone();
}
// ディープコピー
public object DeepClone()
{
var clone = (MyClass)this.MemberwiseClone();
clone.Reference = this.Reference.Clone();
return clone;
}
- シリアライゼーションの利用
public T DeepCloneSerializable<T>()
{
using var stream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
- プロトタイプの登録と管理
public class PrototypeRegistry<T> where T : ICloneable
{
private Dictionary<string, T> _prototypes = new();
public void Register(string key, T prototype)
{
_prototypes[key] = prototype;
}
public T Create(string key)
{
return (T)_prototypes[key].Clone();
}
}
メリット
- 新しいオブジェクトの作成コストを削減
- 実行時のオブジェクト生成が可能
- 複雑なオブジェクトの構築を隠蔽
- 継承の代替として利用可能
デメリット
- 循環参照の処理が困難
- ディープコピーの実装が複雑になる可能性
- クローン処理のオーバーヘッド
- ミュータブルな参照の管理が必要
ベストプラクティス
- コピーコンストラクタの利用
public class MyClass : ICloneable
{
public MyClass(MyClass source)
{
// コピー処理
}
public object Clone()
{
return new MyClass(this);
}
}
- イミュータブルオブジェクトの活用
public class ImmutablePrototype
{
private readonly string _data;
public ImmutablePrototype(string data)
{
_data = data;
}
public ImmutablePrototype Clone()
{
// イミュータブルなので参照をそのまま使用可能
return this;
}
}
- プロトタイプの初期化検証
public class ValidatedPrototype : IPrototype
{
public ValidatedPrototype Clone()
{
var clone = (ValidatedPrototype)MemberwiseClone();
ValidateClone(clone);
return clone;
}
private void ValidateClone(ValidatedPrototype clone)
{
// クローンの検証ロジック
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[13] 2. Bridge(ブリッジ)
[13] 2. Bridge(ブリッジ)ついて説明してください。
Bridgeパターンについて、詳しく解説させていただきます。
// 実装部分のインターフェース
public interface IDeviceImplementation
{
void TurnOn();
void TurnOff();
void SetChannel(int channel);
void SetVolume(int volume);
bool IsEnabled();
int GetChannel();
int GetVolume();
}
// 具象実装(テレビ)
public class Television : IDeviceImplementation
{
private bool isOn;
private int channel = 1;
private int volume = 30;
public void TurnOn()
{
isOn = true;
Console.WriteLine("テレビの電源をON");
}
public void TurnOff()
{
isOn = false;
Console.WriteLine("テレビの電源をOFF");
}
public void SetChannel(int channel)
{
this.channel = channel;
Console.WriteLine($"テレビのチャンネルを{channel}に設定");
}
public void SetVolume(int volume)
{
this.volume = Math.Clamp(volume, 0, 100);
Console.WriteLine($"テレビの音量を{this.volume}に設定");
}
public bool IsEnabled() => isOn;
public int GetChannel() => channel;
public int GetVolume() => volume;
}
// 具象実装(ラジオ)
public class Radio : IDeviceImplementation
{
private bool isOn;
private int channel = 87;
private int volume = 20;
public void TurnOn()
{
isOn = true;
Console.WriteLine("ラジオの電源をON");
}
public void TurnOff()
{
isOn = false;
Console.WriteLine("ラジオの電源をOFF");
}
public void SetChannel(int frequency)
{
this.channel = Math.Clamp(frequency, 76, 108);
Console.WriteLine($"ラジオの周波数を{this.channel}MHzに設定");
}
public void SetVolume(int volume)
{
this.volume = Math.Clamp(volume, 0, 100);
Console.WriteLine($"ラジオの音量を{this.volume}に設定");
}
public bool IsEnabled() => isOn;
public int GetChannel() => channel;
public int GetVolume() => volume;
}
// 抽象化部分
public abstract class RemoteControl
{
protected readonly IDeviceImplementation device;
protected RemoteControl(IDeviceImplementation device)
{
this.device = device;
}
public virtual void PowerButton()
{
if (device.IsEnabled())
{
device.TurnOff();
}
else
{
device.TurnOn();
}
}
public virtual void VolumeUp()
{
device.SetVolume(device.GetVolume() + 10);
}
public virtual void VolumeDown()
{
device.SetVolume(device.GetVolume() - 10);
}
public virtual void ChannelUp()
{
device.SetChannel(device.GetChannel() + 1);
}
public virtual void ChannelDown()
{
device.SetChannel(device.GetChannel() - 1);
}
}
// 拡張された抽象化(高度なリモコン)
public class AdvancedRemoteControl : RemoteControl
{
public AdvancedRemoteControl(IDeviceImplementation device) : base(device) { }
public void Mute()
{
device.SetVolume(0);
Console.WriteLine("ミュート設定");
}
public void SetFavoriteChannel(int channel)
{
Console.WriteLine($"お気に入りチャンネル{channel}に切り替え");
device.SetChannel(channel);
// 追加の設定(音量調整など)
device.SetVolume(25);
}
public override void PowerButton()
{
base.PowerButton();
// 追加の初期化処理
if (device.IsEnabled())
{
device.SetVolume(20);
device.SetChannel(1);
}
}
}
// IoT対応デバイス実装
public class SmartDevice : IDeviceImplementation
{
private bool isOn;
private int channel = 1;
private int volume = 30;
private readonly string deviceId;
private readonly ICloudService cloudService;
public SmartDevice(string deviceId, ICloudService cloudService)
{
this.deviceId = deviceId;
this.cloudService = cloudService;
}
public void TurnOn()
{
isOn = true;
cloudService.LogDeviceState(deviceId, "ON");
Console.WriteLine("スマートデバイスの電源をON");
}
public void TurnOff()
{
isOn = false;
cloudService.LogDeviceState(deviceId, "OFF");
Console.WriteLine("スマートデバイスの電源をOFF");
}
public void SetChannel(int channel)
{
this.channel = channel;
cloudService.LogDeviceState(deviceId, $"Channel: {channel}");
Console.WriteLine($"スマートデバイスのチャンネルを{channel}に設定");
}
public void SetVolume(int volume)
{
this.volume = Math.Clamp(volume, 0, 100);
cloudService.LogDeviceState(deviceId, $"Volume: {this.volume}");
Console.WriteLine($"スマートデバイスの音量を{this.volume}に設定");
}
public bool IsEnabled() => isOn;
public int GetChannel() => channel;
public int GetVolume() => volume;
}
// クラウドサービスインターフェース
public interface ICloudService
{
void LogDeviceState(string deviceId, string state);
}
// 使用例
class Program
{
static void Main()
{
// 通常のテレビとリモコン
IDeviceImplementation tv = new Television();
RemoteControl remote = new RemoteControl(tv);
Console.WriteLine("通常のテレビ操作:");
remote.PowerButton();
remote.VolumeUp();
remote.ChannelUp();
Console.WriteLine("\n--------------------\n");
// ラジオと高度なリモコン
IDeviceImplementation radio = new Radio();
AdvancedRemoteControl advancedRemote = new AdvancedRemoteControl(radio);
Console.WriteLine("高度なラジオ操作:");
advancedRemote.PowerButton();
advancedRemote.SetFavoriteChannel(80);
advancedRemote.Mute();
Console.WriteLine("\n--------------------\n");
// IoTデバイスのテスト
var cloudService = new MockCloudService();
IDeviceImplementation smartDevice = new SmartDevice("DEVICE001", cloudService);
var smartRemote = new AdvancedRemoteControl(smartDevice);
Console.WriteLine("スマートデバイス操作:");
smartRemote.PowerButton();
smartRemote.SetFavoriteChannel(5);
smartRemote.VolumeUp();
}
}
// モッククラウドサービス
public class MockCloudService : ICloudService
{
public void LogDeviceState(string deviceId, string state)
{
Console.WriteLine($"クラウドログ - デバイス {deviceId}: {state}");
}
}
Bridgeパターンの詳細解説
目的
- 抽象化と実装を分離し、独立して変更可能にする
- 機能の階層と実装の階層を分離する
- 実行時に実装を切り替え可能にする
適用すべき状況
抽象化と実装の分離が必要な場合
- クロスプラットフォーム開発
- デバイスドライバー
- GUIフレームワーク
実装の変更が頻繁な場合
- ハードウェア依存の処理
- プラットフォーム固有の実装
- バックエンドサービスの切り替え
継承関係が複雑になる場合の代替として
- 機能と実装の組み合わせが多い
- 継承階層が深くなる可能性がある
避けるべき状況
単一プラットフォームのみの場合
- 実装の変更が不要な場合
- 抽象化が不要な場合
シンプルな階層構造の場合
- 継承で十分な場合
- オーバーエンジニアリングになる可能性
パフォーマンスが重要な場合
- インターフェース呼び出しのオーバーヘッドが問題になる場合
- 直接的な実装が必要な場合
実装上の注意点
- インターフェースの設計
public interface IImplementation
{
void OperationImpl();
}
public abstract class Abstraction
{
protected IImplementation implementation;
public virtual void Operation()
{
implementation.OperationImpl();
}
}
- 実装の切り替え
public class RefinedAbstraction : Abstraction
{
public void ChangeImplementation(IImplementation newImpl)
{
implementation = newImpl;
}
}
- 初期化の保証
public abstract class SafeAbstraction
{
private readonly IImplementation _implementation;
protected SafeAbstraction(IImplementation implementation)
{
_implementation = implementation
?? throw new ArgumentNullException(nameof(implementation));
}
}
メリット
- インターフェースと実装の分離
- 拡張性の向上
- 実装の詳細を隠蔽
- 実行時の実装変更が可能
デメリット
- 設計の複雑化
- インターフェース設計の難しさ
- パフォーマンスへの影響
- コード量の増加
ベストプラクティス
- ファクトリーメソッドとの組み合わせ
public abstract class AbstractionFactory
{
public abstract IImplementation CreateImplementation();
public Abstraction CreateAbstraction()
{
return new ConcreteAbstraction(CreateImplementation());
}
}
- 依存性注入の活用
public class ModernAbstraction
{
private readonly IImplementation _implementation;
public ModernAbstraction(IImplementation implementation)
{
_implementation = implementation;
}
}
- 実装の検証
public class ValidatedAbstraction : Abstraction
{
public override void Operation()
{
if (!IsValid())
{
throw new InvalidOperationException();
}
base.Operation();
}
private bool IsValid()
{
// 検証ロジック
return true;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[14] 3. Chain of Responsibility(責任の連鎖)
[14] 3. Chain of Responsibility(責任の連鎖)ついて説明してください。
Chain of Responsibilityパターンについて、詳しく解説させていただきます。
// リクエストクラス
public class PurchaseRequest
{
public string ItemName { get; }
public decimal Amount { get; }
public string Purpose { get; }
public string Department { get; }
public PurchaseRequest(string itemName, decimal amount, string purpose, string department)
{
ItemName = itemName;
Amount = amount;
Purpose = purpose;
Department = department;
}
}
// ハンドラーの抽象クラス
public abstract class PurchaseHandler
{
protected PurchaseHandler _nextHandler;
protected decimal _approvalLimit;
protected string _handlerName;
protected PurchaseHandler(decimal approvalLimit, string handlerName)
{
_approvalLimit = approvalLimit;
_handlerName = handlerName;
}
public PurchaseHandler SetNext(PurchaseHandler handler)
{
_nextHandler = handler;
return handler;
}
public virtual void Handle(PurchaseRequest request)
{
if (CanHandle(request))
{
Approve(request);
}
else if (_nextHandler != null)
{
Console.WriteLine($"{_handlerName}の承認限度額を超えています。次のレベルに承認を依頼します。");
_nextHandler.Handle(request);
}
else
{
Console.WriteLine("この購入申請を処理できる承認者が見つかりません。");
}
}
protected abstract bool CanHandle(PurchaseRequest request);
protected abstract void Approve(PurchaseRequest request);
}
// 具象ハンドラー:部門マネージャー
public class DepartmentManager : PurchaseHandler
{
public DepartmentManager() : base(10000, "部門マネージャー") { }
protected override bool CanHandle(PurchaseRequest request)
{
return request.Amount <= _approvalLimit;
}
protected override void Approve(PurchaseRequest request)
{
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
// 具象ハンドラー:ディレクター
public class Director : PurchaseHandler
{
public Director() : base(50000, "ディレクター") { }
protected override bool CanHandle(PurchaseRequest request)
{
return request.Amount <= _approvalLimit;
}
protected override void Approve(PurchaseRequest request)
{
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
// 具象ハンドラー:副社長
public class VicePresident : PurchaseHandler
{
public VicePresident() : base(100000, "副社長") { }
protected override bool CanHandle(PurchaseRequest request)
{
return request.Amount <= _approvalLimit;
}
protected override void Approve(PurchaseRequest request)
{
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
// 具象ハンドラー:社長
public class President : PurchaseHandler
{
public President() : base(decimal.MaxValue, "社長") { }
protected override bool CanHandle(PurchaseRequest request)
{
return true; // 社長は金額に関係なく承認可能
}
protected override void Approve(PurchaseRequest request)
{
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
// ロギング機能付きハンドラーデコレーター
public class LoggingHandlerDecorator : PurchaseHandler
{
private readonly PurchaseHandler _handler;
private readonly ILogger _logger;
public LoggingHandlerDecorator(PurchaseHandler handler, ILogger logger)
: base(handler._approvalLimit, handler._handlerName)
{
_handler = handler;
_logger = logger;
}
public override void Handle(PurchaseRequest request)
{
_logger.Log($"購入申請を処理中: {request.ItemName}, 金額: ¥{request.Amount:N0}");
_handler.Handle(request);
_logger.Log($"購入申請の処理完了: {request.ItemName}");
}
protected override bool CanHandle(PurchaseRequest request)
{
return _handler.CanHandle(request);
}
protected override void Approve(PurchaseRequest request)
{
_handler.Approve(request);
}
}
// 非同期ハンドラー
public abstract class AsyncPurchaseHandler
{
protected AsyncPurchaseHandler _nextHandler;
protected decimal _approvalLimit;
protected string _handlerName;
protected AsyncPurchaseHandler(decimal approvalLimit, string handlerName)
{
_approvalLimit = approvalLimit;
_handlerName = handlerName;
}
public AsyncPurchaseHandler SetNext(AsyncPurchaseHandler handler)
{
_nextHandler = handler;
return handler;
}
public async Task HandleAsync(PurchaseRequest request)
{
if (await CanHandleAsync(request))
{
await ApproveAsync(request);
}
else if (_nextHandler != null)
{
Console.WriteLine($"{_handlerName}の承認限度額を超えています。次のレベルに承認を依頼します。");
await _nextHandler.HandleAsync(request);
}
else
{
Console.WriteLine("この購入申請を処理できる承認者が見つかりません。");
}
}
protected abstract Task<bool> CanHandleAsync(PurchaseRequest request);
protected abstract Task ApproveAsync(PurchaseRequest request);
}
// 使用例
class Program
{
static async Task Main()
{
// 承認チェーンの構築
var manager = new DepartmentManager();
var director = new Director();
var vp = new VicePresident();
var president = new President();
manager.SetNext(director).SetNext(vp).SetNext(president);
// 購入申請の処理
Console.WriteLine("1. 少額の購入申請:");
var request1 = new PurchaseRequest("事務用品", 5000, "業務用", "営業部");
manager.Handle(request1);
Console.WriteLine("\n2. 中額の購入申請:");
var request2 = new PurchaseRequest("サーバー機器", 75000, "インフラ更新", "IT部");
manager.Handle(request2);
Console.WriteLine("\n3. 高額の購入申請:");
var request3 = new PurchaseRequest("社用車", 500000, "営業活動用", "営業部");
manager.Handle(request3);
// ロギング機能付きハンドラーの使用
Console.WriteLine("\n4. ロギング機能付きハンドラー:");
var logger = new ConsoleLogger();
var loggingManager = new LoggingHandlerDecorator(manager, logger);
var request4 = new PurchaseRequest("プロジェクター", 15000, "会議室用", "総務部");
loggingManager.Handle(request4);
// 非同期ハンドラーの使用
Console.WriteLine("\n5. 非同期ハンドラー:");
var asyncManager = new AsyncDepartmentManager();
var asyncDirector = new AsyncDirector();
asyncManager.SetNext(asyncDirector);
var request5 = new PurchaseRequest("ソフトウェアライセンス", 30000, "開発用", "IT部");
await asyncManager.HandleAsync(request5);
}
}
// ヘルパークラス
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
}
// 非同期ハンドラーの実装例
public class AsyncDepartmentManager : AsyncPurchaseHandler
{
public AsyncDepartmentManager() : base(10000, "部門マネージャー") { }
protected override async Task<bool> CanHandleAsync(PurchaseRequest request)
{
await Task.Delay(100); // 非同期処理のシミュレーション
return request.Amount <= _approvalLimit;
}
protected override async Task ApproveAsync(PurchaseRequest request)
{
await Task.Delay(100); // 非同期処理のシミュレーション
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
public class AsyncDirector : AsyncPurchaseHandler
{
public AsyncDirector() : base(50000, "ディレクター") { }
protected override async Task<bool> CanHandleAsync(PurchaseRequest request)
{
await Task.Delay(100); // 非同期処理のシミュレーション
return request.Amount <= _approvalLimit;
}
protected override async Task ApproveAsync(PurchaseRequest request)
{
await Task.Delay(100); // 非同期処理のシミュレーション
Console.WriteLine($"{_handlerName}が¥{request.Amount:N0}の{request.ItemName}の購入を承認しました。");
}
}
Chain of Responsibilityパターンの詳細解説
目的
- 要求の送信者と受信者を分離する
- 複数のオブジェクトに要求処理の機会を与える
- 処理の責任を動的に割り当てる
適用すべき状況
複数のオブジェクトが要求を処理できる場合
- 承認フロー
- イベントハンドリング
- エラー処理
要求の処理者が動的に決定される場合
- 権限レベルに基づく処理
- 条件付きの処理フロー
- フォールバック処理
処理の順序が重要な場合
- バリデーションチェーン
- フィルターチェーン
- ミドルウェア
避けるべき状況
処理者が固定的な場合
- 単純な条件分岐で十分な場合
- 処理の流れが静的な場合
即時の応答が必要な場合
- パフォーマンスが重要な場合
- チェーンの遅延が問題になる場合
すべての要求が処理されることを保証する必要がある場合
- エラー処理が重要な場合
- データの整合性が重要な場合
実装上の注意点
- チェーンの構築
public Handler SetNext(Handler handler)
{
_nextHandler = handler;
return handler; // メソッドチェーンを可能にする
}
- 循環参照の防止
protected void ValidateChain(Handler handler)
{
var current = this;
while (current != null)
{
if (current == handler)
throw new InvalidOperationException("循環参照が検出されました");
current = current._nextHandler;
}
}
- デフォルトの処理
public virtual void Handle(Request request)
{
if (_nextHandler != null)
{
_nextHandler.Handle(request);
}
else
{
// デフォルトの処理
HandleDefault(request);
}
}
メリット
- 結合度の低減
- 責任の分割
- 処理の柔軟性
- 単一責任の原則を満たす
デメリット
- 処理が保証されない可能性
- デバッグの困難さ
- パフォーマンスへの影響
- 設定の複雑さ
ベストプラクティス
- ファクトリーメソッドの利用
public class HandlerFactory
{
public static Handler CreateChain()
{
var handler1 = new ConcreteHandler1();
var handler2 = new ConcreteHandler2();
var handler3 = new ConcreteHandler3();
handler1.SetNext(handler2).SetNext(handler3);
return handler1;
}
}
- 例外処理の実装
public void SafeHandle(Request request)
{
try
{
Handle(request);
}
catch (Exception ex)
{
// エラーログの記録
// フォールバック処理
}
}
- チェーンの検証
public class ValidatedHandler : Handler
{
public override void Handle(Request request)
{
if (!IsValid(request))
{
throw new ValidationException();
}
base.Handle(request);
}
protected virtual bool IsValid(Request request)
{
return request != null;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[15] 4. Flyweight(フライウェイト)
[15] 4. Flyweight(フライウェイト)ついて説明してください。
Flyweightパターンについて、詳しく解説させていただきます。
// フライウェイトインターフェース
public interface ICharacter
{
void Display(double x, double y, string color);
}
// 具象フライウェイト
public class Character : ICharacter
{
private readonly char symbol;
private readonly string fontFamily;
private readonly int fontSize;
public Character(char symbol, string fontFamily, int fontSize)
{
this.symbol = symbol;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
public void Display(double x, double y, string color)
{
Console.WriteLine($"文字 '{symbol}' を表示 - 位置: ({x}, {y}), フォント: {fontFamily}, " +
$"サイズ: {fontSize}, 色: {color}");
}
}
// フライウェイトファクトリー
public class CharacterFactory
{
private readonly Dictionary<string, ICharacter> characters = new();
private static readonly object lockObject = new();
public ICharacter GetCharacter(char symbol, string fontFamily, int fontSize)
{
string key = $"{symbol}_{fontFamily}_{fontSize}";
lock (lockObject)
{
if (!characters.ContainsKey(key))
{
characters[key] = new Character(symbol, fontFamily, fontSize);
Console.WriteLine($"新しい文字オブジェクトを作成: {key}");
}
else
{
Console.WriteLine($"既存の文字オブジェクトを再利用: {key}");
}
return characters[key];
}
}
public int GetCharacterCount()
{
return characters.Count;
}
}
// テキストエディタ実装
public class TextEditor
{
private readonly List<(ICharacter Character, double X, double Y, string Color)> displayList = new();
private readonly CharacterFactory factory;
public TextEditor(CharacterFactory factory)
{
this.factory = factory;
}
public void AddText(string text, double startX, double startY, string fontFamily, int fontSize, string color)
{
double currentX = startX;
foreach (char c in text)
{
var character = factory.GetCharacter(c, fontFamily, fontSize);
displayList.Add((character, currentX, startY, color));
currentX += fontSize * 0.6; // 単純な文字間隔の計算
}
}
public void Display()
{
foreach (var item in displayList)
{
item.Character.Display(item.X, item.Y, item.Color);
}
}
public void Clear()
{
displayList.Clear();
}
}
// フライウェイトを使用したパーティクルシステム
public interface IParticle
{
void Update(float x, float y, float scale, float rotation, float alpha);
void Render();
}
public class ParticleType : IParticle
{
private readonly string texture;
private readonly float baseSize;
// 内部状態(共有)
public ParticleType(string texture, float baseSize)
{
this.texture = texture;
this.baseSize = baseSize;
}
// 外部状態を使用する操作
public void Update(float x, float y, float scale, float rotation, float alpha)
{
// パーティクルの状態を更新
Console.WriteLine($"パーティクル更新 - テクスチャ: {texture}, 位置: ({x}, {y}), " +
$"スケール: {scale * baseSize}, 回転: {rotation}, 透明度: {alpha}");
}
public void Render()
{
// パーティクルの描画(実装例)
Console.WriteLine($"パーティクル描画 - テクスチャ: {texture}");
}
}
public class ParticleSystem
{
private readonly Dictionary<string, ParticleType> particleTypes = new();
private readonly List<ParticleInstance> activeParticles = new();
public ParticleType GetParticleType(string texture, float baseSize)
{
string key = $"{texture}_{baseSize}";
if (!particleTypes.ContainsKey(key))
{
particleTypes[key] = new ParticleType(texture, baseSize);
}
return particleTypes[key];
}
public void CreateParticle(string texture, float baseSize, float x, float y)
{
var type = GetParticleType(texture, baseSize);
var instance = new ParticleInstance(type, x, y);
activeParticles.Add(instance);
}
public void UpdateAll()
{
foreach (var particle in activeParticles.ToList())
{
particle.Update();
if (!particle.IsAlive)
{
activeParticles.Remove(particle);
}
}
}
}
public class ParticleInstance
{
private readonly ParticleType type;
private float x, y;
private float scale;
private float rotation;
private float alpha;
private float lifetime;
public bool IsAlive => lifetime > 0;
public ParticleInstance(ParticleType type, float x, float y)
{
this.type = type;
this.x = x;
this.y = y;
this.scale = 1.0f;
this.rotation = 0.0f;
this.alpha = 1.0f;
this.lifetime = 1.0f;
}
public void Update()
{
// パーティクルの状態を更新
lifetime -= 0.016f; // 約60FPS想定
alpha = lifetime;
scale *= 0.99f;
rotation += 0.1f;
type.Update(x, y, scale, rotation, alpha);
if (IsAlive)
{
type.Render();
}
}
}
// 使用例
class Program
{
static void Main()
{
// テキストエディタの使用例
Console.WriteLine("1. テキストエディタの例:");
var factory = new CharacterFactory();
var editor = new TextEditor(factory);
editor.AddText("Hello, World!", 0, 0, "Arial", 12, "Black");
editor.AddText("Hello, World!", 0, 20, "Arial", 12, "Red");
editor.Display();
Console.WriteLine($"作成された文字オブジェクト数: {factory.GetCharacterCount()}");
Console.WriteLine("\n2. パーティクルシステムの例:");
var particleSystem = new ParticleSystem();
// 複数のパーティクルを生成
for (int i = 0; i < 5; i++)
{
particleSystem.CreateParticle("explosion", 1.0f, i * 10, i * 10);
}
// 数フレーム分のシミュレーション
for (int frame = 0; frame < 3; frame++)
{
Console.WriteLine($"\nフレーム {frame}:");
particleSystem.UpdateAll();
}
}
}
Flyweightパターンの詳細解説
目的
- 共有を通じて、大量の細粒度オブジェクトを効率的に扱う
- メモリ使用量を削減する
- パフォーマンスを向上させる
適用すべき状況
大量の類似オブジェクトを使用する場合
- テキストエディタ
- ゲームオブジェクト
- パーティクルシステム
メモリの使用量が問題になる場合
- モバイルアプリケーション
- 大規模データ処理
- リソース制約のある環境
オブジェクトの状態を分離できる場合
- 内部状態と外部状態の区別が明確
- 共有可能な属性がある
避けるべき状況
オブジェクトの数が少ない場合
- 単純なオブジェクト管理で十分
- メモリの制約がない
状態の分離が困難な場合
- 複雑な状態管理が必要
- オブジェクト固有の状態が多い
パフォーマンスがクリティカルでない場合
- シンプルな実装で十分
- メモリ使用量が問題にならない
実装上の注意点
- インスタンス管理
public class FlyweightFactory
{
private readonly Dictionary<string, IFlyweight> _flyweights = new();
public IFlyweight GetFlyweight(string key)
{
lock (_flyweights)
{
if (!_flyweights.ContainsKey(key))
{
_flyweights[key] = new ConcreteFlyweight(key);
}
return _flyweights[key];
}
}
}
- 状態の分離
public interface IFlyweight
{
void Operation(ExtrinsicState state);
}
public class ExtrinsicState
{
public int X { get; set; }
public int Y { get; set; }
// その他の外部状態
}
- スレッドセーフティ
public class ThreadSafeFlyweightFactory
{
private readonly ConcurrentDictionary<string, IFlyweight> _flyweights = new();
public IFlyweight GetFlyweight(string key)
{
return _flyweights.GetOrAdd(key, k => new ConcreteFlyweight(k));
}
}
メリット
- メモリ使用量の削減
- パフォーマンスの向上
- アプリケーションのスケーラビリティ向上
- オブジェクト生成コストの削減
デメリット
- コードの複雑化
- 状態管理の難しさ
- スレッドセーフティの考慮
- デバッグの困難さ
ベストプラクティス
- キーの生成
public class KeyGenerator
{
public static string GenerateKey(params object[] parameters)
{
return string.Join("_", parameters);
}
}
- メモリ管理
public class ManagedFlyweightFactory
{
private readonly WeakReference<Dictionary<string, IFlyweight>> _cache;
public IFlyweight GetFlyweight(string key)
{
Dictionary<string, IFlyweight> cache;
if (!_cache.TryGetTarget(out cache))
{
cache = new Dictionary<string, IFlyweight>();
_cache.SetTarget(cache);
}
// キャッシュの処理
return cache.GetOrAdd(key, CreateFlyweight);
}
}
- プーリングとの組み合わせ
public class FlyweightPool<T> where T : IFlyweight
{
private readonly ConcurrentBag<T> _pool;
private readonly Func<T> _factory;
public T Acquire()
{
return _pool.TryTake(out T item) ? item : _factory();
}
public void Release(T item)
{
_pool.Add(item);
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[16] 5. Interpreter(インタプリタ)
[16] 5. Interpreter(インタプリタ)ついて説明してください。
Interpreterパターンについて、詳しく解説させていただきます。
// 抽象構文木のノードを表す抽象クラス
public abstract class Expression
{
public abstract int Interpret(Context context);
}
// コンテキストクラス(変数の値を保持)
public class Context
{
private readonly Dictionary<string, int> variables = new();
public int GetVariable(string name)
{
return variables.TryGetValue(name, out int value) ? value : 0;
}
public void SetVariable(string name, int value)
{
variables[name] = value;
}
}
// 数値を表現するターミナル式
public class NumberExpression : Expression
{
private readonly int number;
public NumberExpression(int number)
{
this.number = number;
}
public override int Interpret(Context context)
{
return number;
}
}
// 変数を表現するターミナル式
public class VariableExpression : Expression
{
private readonly string name;
public VariableExpression(string name)
{
this.name = name;
}
public override int Interpret(Context context)
{
return context.GetVariable(name);
}
}
// 加算を表現する非ターミナル式
public class AddExpression : Expression
{
private readonly Expression left;
private readonly Expression right;
public AddExpression(Expression left, Expression right)
{
this.left = left;
this.right = right;
}
public override int Interpret(Context context)
{
return left.Interpret(context) + right.Interpret(context);
}
}
// 減算を表現する非ターミナル式
public class SubtractExpression : Expression
{
private readonly Expression left;
private readonly Expression right;
public SubtractExpression(Expression left, Expression right)
{
this.left = left;
this.right = right;
}
public override int Interpret(Context context)
{
return left.Interpret(context) - right.Interpret(context);
}
}
// 乗算を表現する非ターミナル式
public class MultiplyExpression : Expression
{
private readonly Expression left;
private readonly Expression right;
public MultiplyExpression(Expression left, Expression right)
{
this.left = left;
this.right = right;
}
public override int Interpret(Context context)
{
return left.Interpret(context) * right.Interpret(context);
}
}
// パーサークラス
public class Parser
{
private readonly List<string> tokens = new();
private int position;
public Expression Parse(string input)
{
// 入力文字列をトークンに分割
tokens.Clear();
tokens.AddRange(input.Split(' ', StringSplitOptions.RemoveEmptyEntries));
position = 0;
return ParseExpression();
}
private Expression ParseExpression()
{
var left = ParseTerm();
while (position < tokens.Count && (tokens[position] == "+" || tokens[position] == "-"))
{
string op = tokens[position];
position++;
var right = ParseTerm();
if (op == "+")
left = new AddExpression(left, right);
else
left = new SubtractExpression(left, right);
}
return left;
}
private Expression ParseTerm()
{
var left = ParseFactor();
while (position < tokens.Count && tokens[position] == "*")
{
position++;
var right = ParseFactor();
left = new MultiplyExpression(left, right);
}
return left;
}
private Expression ParseFactor()
{
string token = tokens[position];
position++;
if (int.TryParse(token, out int number))
{
return new NumberExpression(number);
}
else
{
return new VariableExpression(token);
}
}
}
// カスタム例外
public class InterpreterException : Exception
{
public InterpreterException(string message) : base(message) { }
}
// 式評価器
public class ExpressionEvaluator
{
private readonly Parser parser;
private readonly Context context;
public ExpressionEvaluator()
{
parser = new Parser();
context = new Context();
}
public void SetVariable(string name, int value)
{
context.SetVariable(name, value);
}
public int Evaluate(string expression)
{
try
{
var parsedExpression = parser.Parse(expression);
return parsedExpression.Interpret(context);
}
catch (Exception ex)
{
throw new InterpreterException($"式の評価中にエラーが発生しました: {ex.Message}");
}
}
}
// 使用例
class Program
{
static void Main()
{
var evaluator = new ExpressionEvaluator();
// 変数の設定
evaluator.SetVariable("x", 5);
evaluator.SetVariable("y", 10);
// 式の評価
Console.WriteLine("式の評価例:");
// 単純な数値計算
string expr1 = "5 + 3";
Console.WriteLine($"{expr1} = {evaluator.Evaluate(expr1)}");
// 変数を含む計算
string expr2 = "x + y";
Console.WriteLine($"{expr2} = {evaluator.Evaluate(expr2)}");
// 複雑な式
string expr3 = "x * y + 2";
Console.WriteLine($"{expr3} = {evaluator.Evaluate(expr3)}");
// 複数の演算子
string expr4 = "5 * x + y - 3";
Console.WriteLine($"{expr4} = {evaluator.Evaluate(expr4)}");
try
{
// 不正な式
string invalidExpr = "x + + y";
Console.WriteLine($"不正な式: {invalidExpr}");
evaluator.Evaluate(invalidExpr);
}
catch (InterpreterException ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
}
Interpreterパターンの詳細解説
目的
- 文法規則をクラスとして表現する
- 言語の文を解釈する
- DSL(ドメイン固有言語)の実装を可能にする
適用すべき状況
簡単な文法を解釈する必要がある場合
- 数式の評価
- 検索クエリの解析
- 設定ファイルの解析
DSLを実装する場合
- カスタムスクリプト言語
- ビジネスルールエンジン
- パターンマッチング
文法規則が頻繁に変更される場合
- 拡張可能な言語処理
- カスタマイズ可能な構文解析
避けるべき状況
複雑な文法の場合
- パーサージェネレータの使用を検討
- 既存の言語処理系の利用を検討
パフォーマンスが重要な場合
- インタプリタのオーバーヘッドが問題
- コンパイル型の処理が必要
文法が固定的な場合
- 既存のライブラリで十分
- カスタム実装が不要
実装上の注意点
- 抽象構文木の構築
public class ExpressionBuilder
{
public Expression BuildTree(string input)
{
var tokens = Tokenize(input);
return BuildExpression(tokens);
}
private Expression BuildExpression(List<string> tokens)
{
// 構文木の構築ロジック
}
}
- コンテキストの管理
public class InterpreterContext
{
private readonly Stack<object> stack = new();
private readonly Dictionary<string, object> symbolTable = new();
public void PushValue(object value)
{
stack.Push(value);
}
public T PopValue<T>()
{
return (T)stack.Pop();
}
}
- エラー処理
public class ParserException : Exception
{
public int Position { get; }
public string Token { get; }
public ParserException(string message, int position, string token)
: base(message)
{
Position = position;
Token = token;
}
}
メリット
- 文法規則の追加・変更が容易
- 文法を階層的に表現可能
- 柔軟な言語処理が可能
- 保守性の向上
デメリット
- 複雑な文法での実装の困難さ
- パフォーマンスの低下
- デバッグの困難さ
- コード量の増加
ベストプラクティス
- ビジターパターンとの組み合わせ
public interface IExpressionVisitor
{
void Visit(NumberExpression expression);
void Visit(OperatorExpression expression);
}
public abstract class Expression
{
public abstract void Accept(IExpressionVisitor visitor);
}
- メモ化の実装
public class CachingExpression : Expression
{
private readonly Expression expression;
private readonly Dictionary<Context, object> cache = new();
public override object Interpret(Context context)
{
if (!cache.ContainsKey(context))
{
cache[context] = expression.Interpret(context);
}
return cache[context];
}
}
- ファクトリーメソッドの使用
public class ExpressionFactory
{
public static Expression CreateExpression(string type, params object[] args)
{
return type switch
{
"number" => new NumberExpression((int)args[0]),
"variable" => new VariableExpression((string)args[0]),
"add" => new AddExpression((Expression)args[0], (Expression)args[1]),
_ => throw new ArgumentException($"Unknown expression type: {type}")
};
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[17] 6. Iterator(イテレータ)
[17] 6. Iterator(イテレータ)ついて説明してください。
Iteratorパターンについて、詳しく解説させていただきます。
// カスタムコレクションのための反復子インターフェース
public interface ICustomIterator<T>
{
bool HasNext();
T Next();
void Reset();
T Current { get; }
}
// 集約オブジェクトのインターフェース
public interface ICustomCollection<T>
{
ICustomIterator<T> GetIterator();
void Add(T item);
int Count { get; }
}
// 双方向反復子インターフェース
public interface IBidirectionalIterator<T> : ICustomIterator<T>
{
bool HasPrevious();
T Previous();
}
// 具体的なコレクションクラス
public class BookCollection : ICustomCollection<Book>
{
private readonly List<Book> books = new();
public void Add(Book book)
{
books.Add(book);
}
public int Count => books.Count;
public ICustomIterator<Book> GetIterator()
{
return new BookIterator(this);
}
public IBidirectionalIterator<Book> GetBidirectionalIterator()
{
return new BookBidirectionalIterator(this);
}
// コレクション内の要素にアクセスするための内部メソッド
internal Book GetBook(int index)
{
return books[index];
}
}
// 本を表すクラス
public class Book
{
public string Title { get; }
public string Author { get; }
public int Year { get; }
public Book(string title, string author, int year)
{
Title = title;
Author = author;
Year = year;
}
public override string ToString()
{
return $"{Title} by {Author} ({Year})";
}
}
// 基本的な反復子の実装
public class BookIterator : ICustomIterator<Book>
{
private readonly BookCollection collection;
private int currentIndex = -1;
public BookIterator(BookCollection collection)
{
this.collection = collection;
}
public bool HasNext()
{
return currentIndex < collection.Count - 1;
}
public Book Next()
{
if (!HasNext())
throw new InvalidOperationException("コレクションの終端に達しました");
return collection.GetBook(++currentIndex);
}
public void Reset()
{
currentIndex = -1;
}
public Book Current
{
get
{
if (currentIndex < 0 || currentIndex >= collection.Count)
throw new InvalidOperationException("現在の位置が無効です");
return collection.GetBook(currentIndex);
}
}
}
// 双方向反復子の実装
public class BookBidirectionalIterator : IBidirectionalIterator<Book>
{
private readonly BookCollection collection;
private int currentIndex = -1;
public BookBidirectionalIterator(BookCollection collection)
{
this.collection = collection;
}
public bool HasNext()
{
return currentIndex < collection.Count - 1;
}
public bool HasPrevious()
{
return currentIndex > 0;
}
public Book Next()
{
if (!HasNext())
throw new InvalidOperationException("コレクションの終端に達しました");
return collection.GetBook(++currentIndex);
}
public Book Previous()
{
if (!HasPrevious())
throw new InvalidOperationException("コレクションの先頭に達しました");
return collection.GetBook(--currentIndex);
}
public void Reset()
{
currentIndex = -1;
}
public Book Current
{
get
{
if (currentIndex < 0 || currentIndex >= collection.Count)
throw new InvalidOperationException("現在の位置が無効です");
return collection.GetBook(currentIndex);
}
}
}
// フィルター機能付き反復子
public class FilteredBookIterator : ICustomIterator<Book>
{
private readonly BookCollection collection;
private readonly Func<Book, bool> predicate;
private int currentIndex = -1;
public FilteredBookIterator(BookCollection collection, Func<Book, bool> predicate)
{
this.collection = collection;
this.predicate = predicate;
FindNextValid();
}
private void FindNextValid()
{
while (currentIndex < collection.Count - 1)
{
if (predicate(collection.GetBook(currentIndex + 1)))
break;
currentIndex++;
}
}
public bool HasNext()
{
int tempIndex = currentIndex;
while (tempIndex < collection.Count - 1)
{
if (predicate(collection.GetBook(tempIndex + 1)))
return true;
tempIndex++;
}
return false;
}
public Book Next()
{
if (!HasNext())
throw new InvalidOperationException("コレクションの終端に達しました");
currentIndex++;
Book current = collection.GetBook(currentIndex);
FindNextValid();
return current;
}
public void Reset()
{
currentIndex = -1;
FindNextValid();
}
public Book Current
{
get
{
if (currentIndex < 0 || currentIndex >= collection.Count)
throw new InvalidOperationException("現在の位置が無効です");
return collection.GetBook(currentIndex);
}
}
}
// 使用例
class Program
{
static void Main()
{
// コレクションの作成と初期化
var library = new BookCollection();
library.Add(new Book("1984", "George Orwell", 1949));
library.Add(new Book("Brave New World", "Aldous Huxley", 1932));
library.Add(new Book("Fahrenheit 451", "Ray Bradbury", 1953));
library.Add(new Book("The Handmaid's Tale", "Margaret Atwood", 1985));
// 基本的な反復子の使用
Console.WriteLine("1. 基本的な反復:");
var iterator = library.GetIterator();
while (iterator.HasNext())
{
Console.WriteLine(iterator.Next());
}
// 双方向反復子の使用
Console.WriteLine("\n2. 双方向反復:");
var bidirectionalIterator = (BookBidirectionalIterator)library.GetBidirectionalIterator();
while (bidirectionalIterator.HasNext())
{
Console.WriteLine(bidirectionalIterator.Next());
}
Console.WriteLine("\n逆方向に反復:");
while (bidirectionalIterator.HasPrevious())
{
Console.WriteLine(bidirectionalIterator.Previous());
}
// フィルター付き反復子の使用
Console.WriteLine("\n3. 1950年以降の本のみを表示:");
var filteredIterator = new FilteredBookIterator(library, book => book.Year >= 1950);
while (filteredIterator.HasNext())
{
Console.WriteLine(filteredIterator.Next());
}
}
}
Iteratorパターンの詳細解説
目的
- コレクションの内部構造を隠蔽しながら要素にアクセスする方法を提供する
- 異なる走査方法を実装可能にする
- コレクションと走査ロジックを分離する
適用すべき状況
複雑なデータ構造を走査する必要がある場合
- ツリー構造
- グラフ構造
- カスタムコレクション
複数の走査方法が必要な場合
- 順方向/逆方向の走査
- フィルター付き走査
- 深さ優先/幅優先探索
コレクションの実装を隠蔽したい場合
- カプセル化の向上
- インターフェースの単純化
- 実装の変更に対する柔軟性
避けるべき状況
単純なコレクションの場合
- 配列や標準的なリスト
- 単一の走査方法で十分な場合
パフォーマンスが重要な場合
- 直接アクセスが必要な場合
- オーバーヘッドが問題になる場合
メモリ使用量が制限される場合
- 反復子オブジェクトの生成が多い場合
- リソースが制限される環境
実装上の注意点
- 反復子の状態管理
public class SafeIterator<T> : IIterator<T>
{
private bool isDisposed;
public T Current
{
get
{
ThrowIfDisposed();
return GetCurrent();
}
}
private void ThrowIfDisposed()
{
if (isDisposed)
throw new ObjectDisposedException(nameof(SafeIterator<T>));
}
}
- 並行修正の検出
public class ConcurrentSafeIterator<T> : IIterator<T>
{
private readonly int version;
private readonly ICollection<T> collection;
public T Next()
{
if (collection.Version != version)
throw new InvalidOperationException("コレクションが変更されました");
return GetNext();
}
}
- リソース管理
public class DisposableIterator<T> : IIterator<T>, IDisposable
{
private bool disposed;
public void Dispose()
{
if (!disposed)
{
// リソースの解放
disposed = true;
}
}
}
メリット
- 単一責任の原則を満たす
- オープン/クローズドの原則を満たす
- 異なる走査アルゴリズムの実装が容易
- メモリ効率の良い走査が可能
デメリット
- 単純なコレクションでは過剰
- 追加のクラスとインターフェースが必要
- 状態管理の複雑さ
- パフォーマンスへの影響
ベストプラクティス
- ジェネリックの活用
public interface IIterator<T>
{
bool HasNext();
T Next();
void Reset();
T Current { get; }
}
- イミュータブルな実装
public class ImmutableIterator<T>
{
private readonly IReadOnlyList<T> items;
private readonly int currentIndex;
public ImmutableIterator<T> Next()
{
return new ImmutableIterator<T>(items, currentIndex + 1);
}
}
- 合成可能な反復子
public class CompositeIterator<T>
{
private readonly IEnumerable<IIterator<T>> iterators;
public T Next()
{
foreach (var iterator in iterators)
{
if (iterator.HasNext())
return iterator.Next();
}
throw new InvalidOperationException();
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[18] 7. Mediator(メディエータ)
[18] 7. Mediator(メディエータ)ついて説明してください。
Mediatorパターンについて、詳しく解説させていただきます。
// メディエーターインターフェース
public interface IChatMediator
{
void SendMessage(string message, User sender);
void AddUser(User user);
void RemoveUser(User user);
void BroadcastMessage(string message);
void DirectMessage(string message, User sender, User recipient);
}
// 抽象ユーザークラス
public abstract class User
{
protected IChatMediator mediator;
public string Name { get; }
public UserStatus Status { get; protected set; }
protected User(string name, IChatMediator mediator)
{
Name = name;
this.mediator = mediator;
Status = UserStatus.Online;
}
public abstract void Send(string message);
public abstract void Receive(string message, User sender);
public abstract void DirectMessage(string message, User recipient);
}
public enum UserStatus
{
Online,
Away,
Busy,
Offline
}
// 具象メディエーター
public class ChatRoom : IChatMediator
{
private readonly List<User> users = new();
private readonly Dictionary<string, List<string>> messageHistory = new();
private readonly ILogger logger;
public ChatRoom(ILogger logger)
{
this.logger = logger;
}
public void AddUser(User user)
{
users.Add(user);
messageHistory[user.Name] = new List<string>();
BroadcastMessage($"システム: {user.Name} が参加しました");
logger.Log($"ユーザー追加: {user.Name}");
}
public void RemoveUser(User user)
{
users.Remove(user);
BroadcastMessage($"システム: {user.Name} が退出しました");
logger.Log($"ユーザー削除: {user.Name}");
}
public void SendMessage(string message, User sender)
{
logger.Log($"メッセージ送信 - 送信者: {sender.Name}, メッセージ: {message}");
foreach (var user in users.Where(u => u != sender))
{
user.Receive(message, sender);
messageHistory[user.Name].Add($"From {sender.Name}: {message}");
}
}
public void BroadcastMessage(string message)
{
logger.Log($"ブロードキャスト: {message}");
foreach (var user in users)
{
user.Receive(message, null);
messageHistory[user.Name].Add(message);
}
}
public void DirectMessage(string message, User sender, User recipient)
{
if (users.Contains(recipient))
{
recipient.Receive($"[DM] {message}", sender);
messageHistory[recipient.Name].Add($"DM from {sender.Name}: {message}");
logger.Log($"DM送信 - 送信者: {sender.Name}, 受信者: {recipient.Name}");
}
else
{
sender.Receive("システム: 指定されたユーザーが見つかりません", null);
}
}
public List<string> GetUserHistory(User user)
{
return messageHistory.TryGetValue(user.Name, out var history)
? history
: new List<string>();
}
}
// 具象ユーザークラス
public class ChatUser : User
{
private readonly List<string> messageHistory = new();
public ChatUser(string name, IChatMediator mediator) : base(name, mediator)
{
}
public override void Send(string message)
{
Console.WriteLine($"{Name} が送信: {message}");
mediator.SendMessage(message, this);
}
public override void Receive(string message, User sender)
{
string formattedMessage = sender != null
? $"{sender.Name} からのメッセージ: {message}"
: message;
Console.WriteLine($"{Name} が受信: {formattedMessage}");
messageHistory.Add(formattedMessage);
}
public override void DirectMessage(string message, User recipient)
{
Console.WriteLine($"{Name} が {recipient.Name} にDMを送信: {message}");
mediator.DirectMessage(message, this, recipient);
}
public void SetStatus(UserStatus newStatus)
{
Status = newStatus;
mediator.BroadcastMessage($"システム: {Name} のステータスが {newStatus} に変更されました");
}
public List<string> GetMessageHistory()
{
return messageHistory.ToList();
}
}
// ロガーインターフェース
public interface ILogger
{
void Log(string message);
}
// コンソールロガー実装
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
}
// 監視ユーザー(特別な権限を持つユーザー)
public class ModeratorUser : ChatUser
{
public ModeratorUser(string name, IChatMediator mediator) : base(name, mediator)
{
}
public void KickUser(User user)
{
mediator.BroadcastMessage($"システム: モデレーター {Name} が {user.Name} をキックしました");
mediator.RemoveUser(user);
}
public override void Receive(string message, User sender)
{
string formattedMessage = sender != null
? $"[MOD] {sender.Name} からのメッセージ: {message}"
: $"[MOD] {message}";
Console.WriteLine($"{Name} が受信: {formattedMessage}");
}
}
// 使用例
class Program
{
static void Main()
{
// チャットルームの設定
var logger = new ConsoleLogger();
var chatRoom = new ChatRoom(logger);
// ユーザーの作成
var john = new ChatUser("John", chatRoom);
var jane = new ChatUser("Jane", chatRoom);
var moderator = new ModeratorUser("Admin", chatRoom);
// ユーザーの追加
chatRoom.AddUser(john);
chatRoom.AddUser(jane);
chatRoom.AddUser(moderator);
Console.WriteLine("\n--- 通常のメッセージ送信 ---");
john.Send("こんにちは、皆さん!");
Console.WriteLine("\n--- ダイレクトメッセージ ---");
jane.DirectMessage("秘密のメッセージです", john);
Console.WriteLine("\n--- ステータス変更 ---");
jane.SetStatus(UserStatus.Busy);
Console.WriteLine("\n--- モデレーター操作 ---");
moderator.Send("全員に警告: チャットルールを守ってください");
Console.WriteLine("\n--- ユーザーのキック ---");
if (moderator is ModeratorUser mod)
{
mod.KickUser(john);
}
// メッセージ送信の試行(キックされたユーザーへ)
Console.WriteLine("\n--- キックされたユーザーへのメッセージ試行 ---");
jane.DirectMessage("まだいますか?", john);
}
}
Mediatorパターンの詳細解説
目的
- オブジェクト間の相互作用をカプセル化する
- オブジェクト間の結合度を低減する
- 相互作用の制御と調整を集中化する
適用すべき状況
オブジェクト間の通信が複雑な場合
- チャットシステム
- フライト制御システム
- GUI部品の連携
多対多の関係を管理する必要がある場合
- イベント処理システム
- 通信ハブ
- メッセージングシステム
オブジェクト間の依存関係を減らしたい場合
- コンポーネントの分離
- モジュール間の通信
- システムの疎結合化
避けるべき状況
単純な通信で十分な場合
- 1対1の関係
- 静的な通信パターン
- 直接通信が適切な場合
メディエーターが複雑になりすぎる場合
- 中央集権的なボトルネック
- 管理が困難になる場合
パフォーマンスが重要な場合
- 低レイテンシーが要求される場合
- 直接通信が必要な場合
実装上の注意点
- メディエーターのインターフェース設計
public interface IMediator
{
void Register(IComponent component);
void Notify(IComponent sender, string @event);
void RemoveComponent(IComponent component);
}
- コンポーネントの管理
private readonly Dictionary<string, IComponent> _components = new();
public void Register(string name, IComponent component)
{
if (!_components.ContainsKey(name))
{
_components.Add(name, component);
component.SetMediator(this);
}
}
- イベント処理
public void HandleEvent(string eventType, object sender, EventArgs e)
{
switch (eventType)
{
case "StateChanged":
NotifyAll(sender, e);
break;
case "Error":
HandleError(sender, e);
break;
default:
throw new ArgumentException($"Unknown event type: {eventType}");
}
}
メリット
- オブジェクト間の結合度の低減
- 相互作用の一元管理
- 個々のコンポーネントの再利用性向上
- 振る舞いの変更が容易
デメリット
- メディエーターの複雑化
- メディエーターへの依存
- パフォーマンスへの影響
- 単一障害点の可能性
ベストプラクティス
- イベントベースの通信
public class EventMediator : IMediator
{
public event EventHandler<MediatorEventArgs> MediatorEvent;
protected virtual void OnMediatorEvent(MediatorEventArgs e)
{
MediatorEvent?.Invoke(this, e);
}
}
- 非同期処理の対応
public interface IAsyncMediator
{
Task NotifyAsync(IComponent sender, string @event);
Task<T> RequestAsync<T>(IComponent sender, string request);
}
- メディエーターの分割
public class SpecializedMediator : BaseMediator
{
private readonly ILogger _logger;
private readonly IErrorHandler _errorHandler;
public override void Notify(IComponent sender, string @event)
{
_logger.Log($"Event from {sender}: {@event}");
base.Notify(sender, @event);
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[19] 8. Memento(メメント)
[19] 8. Memento(メメント)ついて説明してください。
Mementoパターンについて、詳しく解説させていただきます。
// メメントクラス(状態を保存)
public class TextEditorMemento
{
public string Content { get; }
public int CursorPosition { get; }
public string SelectedText { get; }
public DateTime Timestamp { get; }
public TextEditorMemento(string content, int cursorPosition, string selectedText)
{
Content = content;
CursorPosition = cursorPosition;
SelectedText = selectedText;
Timestamp = DateTime.Now;
}
}
// Originator(元の状態を持つオブジェクト)
public class TextEditor
{
private string content;
private int cursorPosition;
private string selectedText;
public string Content
{
get => content;
set
{
content = value;
cursorPosition = value?.Length ?? 0;
}
}
public void SetCursorPosition(int position)
{
cursorPosition = Math.Min(Math.Max(0, position), content?.Length ?? 0);
}
public void SetSelection(string selected)
{
selectedText = selected;
}
// 現在の状態をメメントとして保存
public TextEditorMemento CreateMemento()
{
return new TextEditorMemento(content, cursorPosition, selectedText);
}
// メメントから状態を復元
public void RestoreFromMemento(TextEditorMemento memento)
{
content = memento.Content;
cursorPosition = memento.CursorPosition;
selectedText = memento.SelectedText;
}
public void ShowState()
{
Console.WriteLine($"Content: {content}");
Console.WriteLine($"Cursor Position: {cursorPosition}");
Console.WriteLine($"Selected Text: {selectedText}\n");
}
}
// Caretaker(メメントの管理)
public class TextEditorHistory
{
private readonly Stack<TextEditorMemento> undoStack = new();
private readonly Stack<TextEditorMemento> redoStack = new();
private readonly int maxHistorySize;
public TextEditorHistory(int maxHistorySize = 10)
{
this.maxHistorySize = maxHistorySize;
}
public void SaveState(TextEditor editor)
{
redoStack.Clear(); // 新しい状態が保存されたら、やり直しスタックをクリア
if (undoStack.Count >= maxHistorySize)
{
var oldestState = undoStack.ToList()[0];
undoStack.Clear();
undoStack.Push(oldestState); // 最も古い状態は保持
}
undoStack.Push(editor.CreateMemento());
}
public bool Undo(TextEditor editor)
{
if (undoStack.Count > 0)
{
var currentState = editor.CreateMemento();
redoStack.Push(currentState);
var previousState = undoStack.Pop();
editor.RestoreFromMemento(previousState);
return true;
}
return false;
}
public bool Redo(TextEditor editor)
{
if (redoStack.Count > 0)
{
var currentState = editor.CreateMemento();
undoStack.Push(currentState);
var nextState = redoStack.Pop();
editor.RestoreFromMemento(nextState);
return true;
}
return false;
}
public void ShowHistory()
{
Console.WriteLine("Undo History:");
foreach (var state in undoStack.Reverse())
{
Console.WriteLine($"- {state.Timestamp}: {state.Content}");
}
Console.WriteLine("\nRedo History:");
foreach (var state in redoStack.Reverse())
{
Console.WriteLine($"- {state.Timestamp}: {state.Content}");
}
}
}
// 拡張されたメメントクラス(差分保存)
public class DiffMemento
{
private readonly string baseContent;
private readonly List<TextDiff> diffs;
public DateTime Timestamp { get; }
public DiffMemento(string baseContent, List<TextDiff> diffs)
{
this.baseContent = baseContent;
this.diffs = diffs;
Timestamp = DateTime.Now;
}
public string ApplyDiffs()
{
string result = baseContent;
foreach (var diff in diffs)
{
result = diff.Apply(result);
}
return result;
}
}
public class TextDiff
{
private readonly int position;
private readonly string oldText;
private readonly string newText;
public TextDiff(int position, string oldText, string newText)
{
this.position = position;
this.oldText = oldText;
this.newText = newText;
}
public string Apply(string text)
{
return text.Substring(0, position) + newText +
text.Substring(position + oldText.Length);
}
public TextDiff Reverse()
{
return new TextDiff(position, newText, oldText);
}
}
// 使用例
class Program
{
static void Main()
{
var editor = new TextEditor();
var history = new TextEditorHistory();
// テキストの編集と状態の保存
Console.WriteLine("1. 初期テキストの入力");
editor.Content = "Hello, World!";
editor.SetSelection("World");
editor.ShowState();
history.SaveState(editor);
Console.WriteLine("2. テキストの追加");
editor.Content = "Hello, World! How are you?";
editor.SetSelection("How");
editor.ShowState();
history.SaveState(editor);
Console.WriteLine("3. さらにテキストを追加");
editor.Content = "Hello, World! How are you? I'm fine!";
editor.SetSelection("fine");
editor.ShowState();
history.SaveState(editor);
// 履歴の表示
Console.WriteLine("現在の履歴:");
history.ShowHistory();
// Undo操作
Console.WriteLine("\n4. Undo操作の実行");
if (history.Undo(editor))
{
editor.ShowState();
}
Console.WriteLine("5. もう一度Undo");
if (history.Undo(editor))
{
editor.ShowState();
}
// Redo操作
Console.WriteLine("6. Redo操作の実行");
if (history.Redo(editor))
{
editor.ShowState();
}
}
}
Mementoパターンの詳細解説
目的
- オブジェクトの内部状態を保存・復元する
- カプセル化を破壊せずに状態のスナップショットを作成
- Undo/Redo機能の実装を可能にする
適用すべき状況
状態の復元が必要な場合
- テキストエディタ
- グラフィックエディタ
- ゲームの状態管理
チェックポイントが必要な場合
- トランザクション処理
- ワークフロー管理
- 段階的な処理
操作の取り消しが必要な場合
- Undo/Redo機能
- ロールバック機能
- バージョン管理
避けるべき状況
状態が単純な場合
- 基本的なデータ型のみ
- 履歴管理が不要な場合
メモリ使用量が制限される場合
- 大量のスナップショット
- リソースの制約
状態の保存が頻繁な場合
- パフォーマンスへの影響
- ストレージの制約
実装上の注意点
- メモリ管理
public class MemoryEfficientHistory
{
private readonly int maxStates;
private readonly LinkedList<IMemento> states;
public void AddState(IMemento state)
{
if (states.Count >= maxStates)
{
states.RemoveFirst();
}
states.AddLast(state);
}
}
- 状態の検証
public class ValidatedMemento
{
public void Restore(IOriginator originator)
{
if (!ValidateState())
{
throw new InvalidOperationException("Invalid state");
}
// 状態の復元
}
private bool ValidateState()
{
// 状態の検証ロジック
return true;
}
}
- 永続化
public class PersistentMemento : IMemento
{
public void SaveToFile(string path)
{
using var stream = File.Create(path);
var formatter = new BinaryFormatter();
formatter.Serialize(stream, this);
}
public static IMemento LoadFromFile(string path)
{
using var stream = File.OpenRead(path);
var formatter = new BinaryFormatter();
return (IMemento)formatter.Deserialize(stream);
}
}
メリット
- カプセル化を維持
- 状態の保存と復元が容易
- 操作の取り消しが可能
- 状態の履歴管理が可能
デメリット
- メモリ使用量の増加
- 複雑な状態の保存が困難
- パフォーマンスへの影響
- 実装の複雑化
ベストプラクティス
- 差分保存
public class DeltaMemento
{
private readonly Dictionary<string, object> changes;
public void SaveDelta(object originalState, object newState)
{
// 変更された値のみを保存
foreach (var prop in GetChangedProperties(originalState, newState))
{
changes[prop.Name] = prop.GetValue(newState);
}
}
}
- 圧縮保存
public class CompressedMemento
{
private byte[] compressedState;
public void SaveState(object state)
{
var serialized = SerializeState(state);
compressedState = Compress(serialized);
}
private byte[] Compress(byte[] data)
{
// 圧縮ロジック
return data;
}
}
- 非同期保存
public class AsyncMemento
{
public async Task SaveStateAsync(IOriginator originator)
{
var state = originator.CreateMemento();
await SaveToDatabaseAsync(state);
}
private async Task SaveToDatabaseAsync(IMemento state)
{
// データベースへの非同期保存
await Task.Delay(100);
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[20] 9. State(ステート)
[20] 9. State(ステート)ついて説明してください。
Stateパターンについて、詳しく解説させていただきます。
// 注文の状態を表す抽象クラス
public abstract class OrderState
{
protected Order order;
public void SetOrder(Order order)
{
this.order = order;
}
public abstract void ProcessOrder();
public abstract void CancelOrder();
public abstract void ShipOrder();
public abstract string GetStateName();
}
// コンテキストクラス(注文)
public class Order
{
private OrderState currentState;
public string OrderId { get; }
public List<string> OrderHistory { get; }
public decimal Amount { get; }
public Order(string orderId, decimal amount)
{
OrderId = orderId;
Amount = amount;
OrderHistory = new List<string>();
SetState(new NewOrderState());
}
public void SetState(OrderState state)
{
currentState = state;
currentState.SetOrder(this);
OrderHistory.Add($"状態が {state.GetStateName()} に変更されました");
}
public void Process()
{
currentState.ProcessOrder();
}
public void Cancel()
{
currentState.CancelOrder();
}
public void Ship()
{
currentState.ShipOrder();
}
public void AddToHistory(string message)
{
OrderHistory.Add($"{DateTime.Now}: {message}");
}
public void DisplayOrder()
{
Console.WriteLine($"\n注文ID: {OrderId}");
Console.WriteLine($"金額: ¥{Amount:N0}");
Console.WriteLine($"現在の状態: {currentState.GetStateName()}");
Console.WriteLine("履歴:");
foreach (var entry in OrderHistory)
{
Console.WriteLine($"- {entry}");
}
}
}
// 具象状態クラス:新規注文
public class NewOrderState : OrderState
{
public override void ProcessOrder()
{
order.AddToHistory("注文の処理を開始します");
order.SetState(new ProcessingOrderState());
}
public override void CancelOrder()
{
order.AddToHistory("新規注文をキャンセルしました");
order.SetState(new CancelledOrderState());
}
public override void ShipOrder()
{
order.AddToHistory("エラー: 新規注文は出荷できません");
}
public override string GetStateName() => "新規注文";
}
// 具象状態クラス:処理中
public class ProcessingOrderState : OrderState
{
public override void ProcessOrder()
{
order.AddToHistory("注文は既に処理中です");
}
public override void CancelOrder()
{
order.AddToHistory("処理中の注文をキャンセルしました");
order.SetState(new CancelledOrderState());
}
public override void ShipOrder()
{
order.AddToHistory("注文の処理が完了し、出荷を開始します");
order.SetState(new ShippedOrderState());
}
public override string GetStateName() => "処理中";
}
// 具象状態クラス:出荷済み
public class ShippedOrderState : OrderState
{
public override void ProcessOrder()
{
order.AddToHistory("エラー: 出荷済みの注文は処理できません");
}
public override void CancelOrder()
{
order.AddToHistory("エラー: 出荷済みの注文はキャンセルできません");
}
public override void ShipOrder()
{
order.AddToHistory("エラー: 注文は既に出荷されています");
}
public override string GetStateName() => "出荷済み";
}
// 具象状態クラス:キャンセル済み
public class CancelledOrderState : OrderState
{
public override void ProcessOrder()
{
order.AddToHistory("エラー: キャンセル済みの注文は処理できません");
}
public override void CancelOrder()
{
order.AddToHistory("エラー: 注文は既にキャンセルされています");
}
public override void ShipOrder()
{
order.AddToHistory("エラー: キャンセル済みの注文は出荷できません");
}
public override string GetStateName() => "キャンセル済み";
}
// レトライ可能な状態の実装例
public class RetryableProcessingState : OrderState
{
private int retryCount = 0;
private const int MaxRetries = 3;
public override void ProcessOrder()
{
if (retryCount < MaxRetries)
{
retryCount++;
order.AddToHistory($"処理を再試行します({retryCount}/{MaxRetries})");
// 処理ロジック
if (/* 処理成功の条件 */ true)
{
order.SetState(new ProcessingOrderState());
}
}
else
{
order.AddToHistory("最大再試行回数を超過しました");
order.SetState(new FailedOrderState());
}
}
// その他のメソッドの実装
public override void CancelOrder()
{
order.AddToHistory("再試行中の処理をキャンセルしました");
order.SetState(new CancelledOrderState());
}
public override void ShipOrder()
{
order.AddToHistory("エラー: 再試行中の注文は出荷できません");
}
public override string GetStateName() => "再試行中";
}
// 失敗状態の実装
public class FailedOrderState : OrderState
{
public override void ProcessOrder()
{
order.AddToHistory("失敗した注文を再処理します");
order.SetState(new RetryableProcessingState());
}
public override void CancelOrder()
{
order.AddToHistory("失敗した注文をキャンセルしました");
order.SetState(new CancelledOrderState());
}
public override void ShipOrder()
{
order.AddToHistory("エラー: 失敗した注文は出荷できません");
}
public override string GetStateName() => "失敗";
}
// 使用例
class Program
{
static void Main()
{
// 正常なフロー
Console.WriteLine("1. 正常な注文フロー:");
var order1 = new Order("ORD001", 5000);
order1.Process();
order1.Ship();
order1.DisplayOrder();
// キャンセルフロー
Console.WriteLine("\n2. キャンセルフロー:");
var order2 = new Order("ORD002", 3000);
order2.Process();
order2.Cancel();
order2.Ship(); // エラーになるはず
order2.DisplayOrder();
// 無効な操作
Console.WriteLine("\n3. 無効な操作:");
var order3 = new Order("ORD003", 7000);
order3.Ship(); // 新規注文は直接出荷できない
order3.DisplayOrder();
// 再試行が必要なケース
Console.WriteLine("\n4. 再試行が必要なケース:");
var order4 = new Order("ORD004", 10000);
order4.SetState(new RetryableProcessingState());
order4.Process();
order4.Process();
order4.Process();
order4.Process(); // 最大再試行回数を超過
order4.DisplayOrder();
}
}
Stateパターンの詳細解説
目的
- オブジェクトの内部状態に応じて振る舞いを変更する
- 状態遷移のロジックをカプセル化する
- 状態依存の処理を分離する
適用すべき状況
オブジェクトの振る舞いが状態に依存する場合
- 注文処理システム
- ワークフロー管理
- ゲームの状態管理
条件分岐が複雑になる場合
- 多数の状態が存在
- 状態遷移が複雑
- if-else文の代替として
状態遷移のルールを明確にしたい場合
- ビジネスルールの実装
- プロセス制御
- エラー処理
避けるべき状況
状態の数が少ない場合
- 単純な条件分岐で十分
- オーバーエンジニアリングになる可能性
状態遷移が固定的な場合
- 変更の可能性が低い
- 単純な状態管理
一時的な状態変化の場合
- 永続的な状態管理が不要
- 簡単なフラグで管理可能
実装上の注意点
- 状態遷移の検証
protected void ValidateTransition(State newState)
{
if (!IsValidTransition(newState))
{
throw new InvalidOperationException(
$"Invalid state transition from {currentState} to {newState}");
}
}
- 状態の永続化
public class PersistableState
{
public void SaveState()
{
var stateData = new StateData
{
StateName = GetType().Name,
Timestamp = DateTime.Now
};
// 状態の保存ロジック
}
}
- 並行処理の考慮
public class ThreadSafeContext
{
private volatile State _state;
private readonly object _lockObject = new();
public void TransitionTo(State newState)
{
lock (_lockObject)
{
_state = newState;
_state.SetContext(this);
}
}
}
メリット
- 単一責任の原則を満たす
- 新しい状態の追加が容易
- 状態遷移ロジックの集中管理
- コードの可読性向上
デメリット
- 状態クラスの数が増加
- シンプルな場合は過剰
- 状態遷移の追跡が困難
- コンテキストとの結合
ベストプラクティス
- 状態遷移テーブルの使用
public class StateTransitionTable
{
private readonly Dictionary<(Type From, Type To), bool> transitions = new();
public void AddTransition<TFrom, TTo>(bool allowed)
where TFrom : State
where TTo : State
{
transitions[(typeof(TFrom), typeof(TTo))] = allowed;
}
}
- イベント駆動の状態遷移
public class EventDrivenState
{
public event EventHandler<StateChangeEventArgs> StateChanged;
protected virtual void OnStateChanged(State oldState, State newState)
{
StateChanged?.Invoke(this, new StateChangeEventArgs(oldState, newState));
}
}
- 状態履歴の管理
public class StateHistory
{
private readonly Stack<State> stateHistory = new();
public void PushState(State state)
{
stateHistory.Push(state);
}
public State PopState()
{
return stateHistory.Count > 0 ? stateHistory.Pop() : null;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[21] 10. Template Method(テンプレートメソッド)
[21] 10. Template Method(テンプレートメソッド)ついて説明してください。
Template Methodパターンについて、詳しく解説させていただきます。
// データ処理の抽象基底クラス
public abstract class DataMinerTemplate
{
// テンプレートメソッド
public void Mine(string path)
{
OpenFile(path);
ExtractData();
ParseData();
AnalyzeData();
if (ShouldSendReport())
{
SendReport();
}
CloseFile();
}
// 抽象メソッド(サブクラスで必ず実装)
protected abstract void ExtractData();
protected abstract void ParseData();
protected abstract void AnalyzeData();
// フックメソッド(オプショナルな振る舞い)
protected virtual bool ShouldSendReport()
{
return true;
}
// 共通処理
protected virtual void OpenFile(string path)
{
Console.WriteLine($"ファイルを開きます: {path}");
}
protected virtual void SendReport()
{
Console.WriteLine("レポートを送信します");
}
protected virtual void CloseFile()
{
Console.WriteLine("ファイルを閉じます");
}
}
// PDF用のデータマイナー
public class PDFDataMiner : DataMinerTemplate
{
protected override void ExtractData()
{
Console.WriteLine("PDFからデータを抽出します");
}
protected override void ParseData()
{
Console.WriteLine("PDFデータをパースします");
}
protected override void AnalyzeData()
{
Console.WriteLine("PDFデータを分析します");
}
}
// CSV用のデータマイナー
public class CSVDataMiner : DataMinerTemplate
{
private readonly bool sendReport;
public CSVDataMiner(bool sendReport = true)
{
this.sendReport = sendReport;
}
protected override void ExtractData()
{
Console.WriteLine("CSVからデータを抽出します");
}
protected override void ParseData()
{
Console.WriteLine("CSVデータをパースします");
}
protected override void AnalyzeData()
{
Console.WriteLine("CSVデータを分析します");
}
protected override bool ShouldSendReport()
{
return sendReport;
}
}
// データベース接続の抽象基底クラス
public abstract class DatabaseConnectionTemplate : IDisposable
{
private bool isConnected;
protected ILogger logger;
protected DatabaseConnectionTemplate(ILogger logger)
{
this.logger = logger;
}
// テンプレートメソッド
public bool Connect(string connectionString)
{
try
{
ValidateConnectionString(connectionString);
logger.Log("接続を開始します...");
if (OpenConnection(connectionString))
{
isConnected = true;
InitializeConnection();
logger.Log("接続が確立されました");
return true;
}
}
catch (Exception ex)
{
logger.Log($"接続エラー: {ex.Message}");
isConnected = false;
}
return false;
}
// 抽象メソッド
protected abstract bool OpenConnection(string connectionString);
protected abstract void InitializeConnection();
protected abstract void CloseConnection();
// フックメソッド
protected virtual void ValidateConnectionString(string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentException("接続文字列が無効です");
}
public void Dispose()
{
if (isConnected)
{
logger.Log("接続を閉じます...");
CloseConnection();
isConnected = false;
}
}
}
// SQLServer接続の実装
public class SqlServerConnection : DatabaseConnectionTemplate
{
public SqlServerConnection(ILogger logger) : base(logger) { }
protected override bool OpenConnection(string connectionString)
{
Console.WriteLine("SQL Serverに接続します");
// 実際の接続処理
return true;
}
protected override void InitializeConnection()
{
Console.WriteLine("SQL Server接続を初期化します");
}
protected override void CloseConnection()
{
Console.WriteLine("SQL Server接続を閉じます");
}
protected override void ValidateConnectionString(string connectionString)
{
base.ValidateConnectionString(connectionString);
if (!connectionString.Contains("Server="))
throw new ArgumentException("SQL Server接続文字列が無効です");
}
}
// MySQL接続の実装
public class MySqlConnection : DatabaseConnectionTemplate
{
public MySqlConnection(ILogger logger) : base(logger) { }
protected override bool OpenConnection(string connectionString)
{
Console.WriteLine("MySQLに接続します");
// 実際の接続処理
return true;
}
protected override void InitializeConnection()
{
Console.WriteLine("MySQL接続を初期化します");
}
protected override void CloseConnection()
{
Console.WriteLine("MySQL接続を閉じます");
}
}
// ロガーインターフェース
public interface ILogger
{
void Log(string message);
}
// コンソールロガーの実装
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}");
}
}
// 使用例
class Program
{
static void Main()
{
// データマイナーの使用例
Console.WriteLine("1. PDFデータマイナーのテスト:");
var pdfMiner = new PDFDataMiner();
pdfMiner.Mine("sample.pdf");
Console.WriteLine("\n2. CSVデータマイナーのテスト (レポートあり):");
var csvMiner = new CSVDataMiner(true);
csvMiner.Mine("sample.csv");
Console.WriteLine("\n3. CSVデータマイナーのテスト (レポートなし):");
var csvMinerNoReport = new CSVDataMiner(false);
csvMinerNoReport.Mine("sample.csv");
// データベース接続の使用例
var logger = new ConsoleLogger();
Console.WriteLine("\n4. SQL Server接続のテスト:");
using (var sqlConnection = new SqlServerConnection(logger))
{
sqlConnection.Connect("Server=localhost;Database=testdb;");
}
Console.WriteLine("\n5. MySQL接続のテスト:");
using (var mysqlConnection = new MySqlConnection(logger))
{
mysqlConnection.Connect("Server=localhost;Database=testdb;");
}
}
}
Template Methodパターンの詳細解説
目的
- アルゴリズムの骨格を定義し、一部のステップをサブクラスで実装可能にする
- 共通の処理を基底クラスで一元管理する
- アルゴリズムの構造を保ちながら、特定のステップをカスタマイズ可能にする
適用すべき状況
共通のアルゴリズム構造がある場合
- データ処理フロー
- 初期化処理
- レポート生成
処理の一部だけが異なる場合
- ファイル形式ごとの処理
- データベース接続
- ドキュメント生成
コードの重複を避けたい場合
- 類似した処理の共通化
- メンテナンス性の向上
- 一貫性の確保
避けるべき状況
アルゴリズムの構造が大きく異なる場合
- 共通部分が少ない
- 処理フローが異なる
柔軟性が重要な場合
- 実行時の動的な変更が必要
- 処理順序の変更が必要
基底クラスが複雑になりすぎる場合
- 責務が多すぎる
- メンテナンスが困難
実装上の注意点
- フックメソッドの提供
protected virtual bool PreProcess()
{
return true; // デフォルトの実装
}
protected virtual void PostProcess()
{
// デフォルトは何もしない
}
- 抽象メソッドの設計
public abstract class TemplateBase
{
protected abstract void RequiredStep();
protected virtual void OptionalStep()
{
// デフォルト実装
}
}
- エラー処理
public void TemplateMethod()
{
try
{
BeforeProcess();
Process();
AfterProcess();
}
catch (Exception ex)
{
HandleError(ex);
throw;
}
}
メリット
- コードの再利用性の向上
- 共通処理の一元管理
- 拡張ポイントの明確化
- 変更の影響範囲の限定
デメリット
- 継承による制約
- 柔軟性の制限
- 基底クラスへの依存
- 理解の難しさ
ベストプラクティス
- ドキュメント化
/// <summary>
/// テンプレートメソッドの基本実装
/// </summary>
public abstract class DocumentedTemplate
{
/// <summary>
/// サブクラスで必ず実装する必要がある処理
/// </summary>
protected abstract void RequiredOperation();
/// <summary>
/// オプショナルなフック処理
/// </summary>
protected virtual void OptionalOperation() { }
}
- デフォルト実装の提供
public abstract class RobustTemplate
{
protected virtual void Initialize()
{
// 基本的な初期化処理
}
protected virtual void Cleanup()
{
// 基本的なクリーンアップ処理
}
}
- パラメータ化
public abstract class ConfigurableTemplate
{
protected readonly Configuration config;
protected ConfigurableTemplate(Configuration config)
{
this.config = config;
}
protected virtual bool ShouldExecuteStep()
{
return config.IsStepEnabled;
}
}
次のパターンについて説明が必要な場合は、お申し付けください。
[22] 11. Visitor(ビジター)
[22] 11. Visitor(ビジター)ついて説明してください。
Visitorパターンについて、詳しく解説させていただきます。
// 抽象訪問者インターフェース
public interface IDocumentVisitor
{
void VisitParagraph(Paragraph paragraph);
void VisitHeading(Heading heading);
void VisitImage(Image image);
void VisitTable(Table table);
}
// 抽象要素インターフェース
public interface IDocumentElement
{
void Accept(IDocumentVisitor visitor);
string GetContent();
}
// 具象要素クラス群
public class Paragraph : IDocumentElement
{
private readonly string text;
public Paragraph(string text)
{
this.text = text;
}
public void Accept(IDocumentVisitor visitor)
{
visitor.VisitParagraph(this);
}
public string GetContent() => text;
}
public class Heading : IDocumentElement
{
private readonly string text;
private readonly int level;
public Heading(string text, int level)
{
this.text = text;
this.level = level;
}
public void Accept(IDocumentVisitor visitor)
{
visitor.VisitHeading(this);
}
public string GetContent() => text;
public int GetLevel() => level;
}
public class Image : IDocumentElement
{
private readonly string url;
private readonly string altText;
public Image(string url, string altText)
{
this.url = url;
this.altText = altText;
}
public void Accept(IDocumentVisitor visitor)
{
visitor.VisitImage(this);
}
public string GetContent() => url;
public string GetAltText() => altText;
}
public class Table : IDocumentElement
{
private readonly List<List<string>> data;
private readonly List<string> headers;
public Table(List<string> headers, List<List<string>> data)
{
this.headers = headers;
this.data = data;
}
public void Accept(IDocumentVisitor visitor)
{
visitor.VisitTable(this);
}
public string GetContent() => "Table";
public List<string> GetHeaders() => headers;
public List<List<string>> GetData() => data;
}
// 文書クラス
public class Document
{
private readonly List<IDocumentElement> elements = new();
public void AddElement(IDocumentElement element)
{
elements.Add(element);
}
public void Accept(IDocumentVisitor visitor)
{
foreach (var element in elements)
{
element.Accept(visitor);
}
}
}
// HTML変換訪問者
public class HtmlVisitor : IDocumentVisitor
{
private readonly StringBuilder html = new();
public void VisitParagraph(Paragraph paragraph)
{
html.AppendLine($"<p>{paragraph.GetContent()}</p>");
}
public void VisitHeading(Heading heading)
{
html.AppendLine($"<h{heading.GetLevel()}>{heading.GetContent()}</h{heading.GetLevel()}>");
}
public void VisitImage(Image image)
{
html.AppendLine($"<img src=\"{image.GetContent()}\" alt=\"{image.GetAltText()}\">");
}
public void VisitTable(Table table)
{
html.AppendLine("<table>");
// ヘッダー
html.AppendLine("<thead><tr>");
foreach (var header in table.GetHeaders())
{
html.AppendLine($"<th>{header}</th>");
}
html.AppendLine("</tr></thead>");
// データ
html.AppendLine("<tbody>");
foreach (var row in table.GetData())
{
html.AppendLine("<tr>");
foreach (var cell in row)
{
html.AppendLine($"<td>{cell}</td>");
}
html.AppendLine("</tr>");
}
html.AppendLine("</tbody>");
html.AppendLine("</table>");
}
public string GetHtml() => html.ToString();
}
// Markdown変換訪問者
public class MarkdownVisitor : IDocumentVisitor
{
private readonly StringBuilder markdown = new();
public void VisitParagraph(Paragraph paragraph)
{
markdown.AppendLine($"{paragraph.GetContent()}\n");
}
public void VisitHeading(Heading heading)
{
markdown.AppendLine($"{new string('#', heading.GetLevel())} {heading.GetContent()}\n");
}
public void VisitImage(Image image)
{
markdown.AppendLine($"![{image.GetAltText()}]({image.GetContent()})\n");
}
public void VisitTable(Table table)
{
// ヘッダー
markdown.AppendLine(string.Join(" | ", table.GetHeaders()));
markdown.AppendLine(string.Join(" | ", table.GetHeaders().Select(_ => "---")));
// データ
foreach (var row in table.GetData())
{
markdown.AppendLine(string.Join(" | ", row));
}
markdown.AppendLine();
}
public string GetMarkdown() => markdown.ToString();
}
// 統計収集訪問者
public class StatisticsVisitor : IDocumentVisitor
{
private int paragraphCount;
private int wordCount;
private int headingCount;
private int imageCount;
private int tableCount;
private int tableCellCount;
public void VisitParagraph(Paragraph paragraph)
{
paragraphCount++;
wordCount += CountWords(paragraph.GetContent());
}
public void VisitHeading(Heading heading)
{
headingCount++;
wordCount += CountWords(heading.GetContent());
}
public void VisitImage(Image image)
{
imageCount++;
}
public void VisitTable(Table table)
{
tableCount++;
tableCellCount += table.GetData().Sum(row => row.Count);
}
private static int CountWords(string text)
{
return text.Split(new[] { ' ', '\t', '\n' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
public DocumentStatistics GetStatistics()
{
return new DocumentStatistics
{
ParagraphCount = paragraphCount,
WordCount = wordCount,
HeadingCount = headingCount,
ImageCount = imageCount,
TableCount = tableCount,
TableCellCount = tableCellCount
};
}
}
public class DocumentStatistics
{
public int ParagraphCount { get; set; }
public int WordCount { get; set; }
public int HeadingCount { get; set; }
public int ImageCount { get; set; }
public int TableCount { get; set; }
public int TableCellCount { get; set; }
public override string ToString()
{
return $"Document Statistics:\n" +
$"Paragraphs: {ParagraphCount}\n" +
$"Words: {WordCount}\n" +
$"Headings: {HeadingCount}\n" +
$"Images: {ImageCount}\n" +
$"Tables: {TableCount}\n" +
$"Table Cells: {TableCellCount}";
}
}
// 使用例
class Program
{
static void Main()
{
// ドキュメントの作成
var document = new Document();
document.AddElement(new Heading("Sample Document", 1));
document.AddElement(new Paragraph("This is a sample paragraph."));
document.AddElement(new Image("sample.jpg", "Sample Image"));
document.AddElement(new Table(
new List<string> { "Header 1", "Header 2" },
new List<List<string>> {
new() { "Cell 1", "Cell 2" },
new() { "Cell 3", "Cell 4" }
}
));
// HTML形式への変換
Console.WriteLine("1. HTML形式:");
var htmlVisitor = new HtmlVisitor();
document.Accept(htmlVisitor);
Console.WriteLine(htmlVisitor.GetHtml());
// Markdown形式への変換
Console.WriteLine("\n2. Markdown形式:");
var markdownVisitor = new MarkdownVisitor();
document.Accept(markdownVisitor);
Console.WriteLine(markdownVisitor.GetMarkdown());
// 統計情報の収集
Console.WriteLine("\n3. 統計情報:");
var statisticsVisitor = new StatisticsVisitor();
document.Accept(statisticsVisitor);
Console.WriteLine(statisticsVisitor.GetStatistics());
}
}
Visitorパターンの詳細解説
目的
- オブジェクト構造から処理を分離する
- 新しい操作を追加しやすくする
- データ構造と処理を分離する
適用すべき状況
複雑なオブジェクト構造に対する操作が必要な場合
- ドキュメント処理
- 抽象構文木の処理
- グラフィカル要素の処理
処理を頻繁に追加する必要がある場合
- データ変換
- レポート生成
- 分析処理
データ構造が安定している場合
- クラス階層が固定的
- インターフェースが安定
避けるべき状況
データ構造が頻繁に変更される場合
- クラスの追加が多い
- インターフェースの変更が多い
処理が単純な場合
- 単一の操作のみ
- シンプルな変換
カプセル化が重要な場合
- 内部データへのアクセスが制限される
- セキュリティ上の制約
実装上の注意点
- 訪問者のインターフェース設計
public interface IVisitor<T>
{
void Visit(T element);
void VisitChildren(IEnumerable<T> children);
}
- 双方ディスパッチ
public interface IElement
{
void Accept(IVisitor visitor);
}
public class ConcreteElement : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
- 状態管理
public class StatefulVisitor : IVisitor
{
private readonly Stack<object> state = new();
protected void PushState(object item)
{
state.Push(item);
}
protected T PopState<T>()
{
return (T)state.Pop();
}
}
メリット
- 単一責任の原則を満たす
- オープン/クローズドの原則を満たす
- 新しい操作の追加が容易
- 関連する処理をグループ化
デメリット
- データ構造の変更が困難
- 実装が複雑になりやすい
- カプセル化の破壊
- クラス階層の依存
ベストプラクティス
- 汎用ビジター
public interface IGenericVisitor
{
void Visit<T>(T element) where T : IElement;
}
- 非同期対応
public interface IAsyncVisitor
{
Task VisitAsync(IElement element);
Task VisitChildrenAsync(IEnumerable<IElement> children);
}
- 結果の集約
public interface IVisitor<TResult>
{
TResult Visit(IElement element);
IEnumerable<TResult> VisitAll(IEnumerable<IElement> elements);
}
次のパターンについて説明が必要な場合は、お申し付けください。
[23] 12. Façade(ファサード)
[23] 12. Façade(ファサード)ついて説明してください。
Façadeパターンについて、詳しく解説させていただきます。
// 複雑なサブシステムのコンポーネント群
public class EmailService
{
public void ValidateEmail(string email)
{
Console.WriteLine($"メールアドレスを検証: {email}");
if (!email.Contains("@"))
throw new ArgumentException("無効なメールアドレス");
}
public void SendEmail(string to, string subject, string body)
{
Console.WriteLine($"メール送信: To={to}, Subject={subject}");
}
}
public class UserDatabase
{
private readonly Dictionary<string, UserData> users = new();
public void SaveUser(UserData user)
{
Console.WriteLine($"ユーザーを保存: {user.Email}");
users[user.Email] = user;
}
public bool UserExists(string email)
{
return users.ContainsKey(email);
}
public UserData GetUser(string email)
{
return users.TryGetValue(email, out var user) ? user : null;
}
}
public class NotificationService
{
public void NotifyAdmin(string message)
{
Console.WriteLine($"管理者に通知: {message}");
}
public void NotifyUser(string userId, string message)
{
Console.WriteLine($"ユーザー {userId} に通知: {message}");
}
}
public class Logger
{
public void LogInfo(string message)
{
Console.WriteLine($"[INFO] {DateTime.Now}: {message}");
}
public void LogError(string message, Exception ex = null)
{
Console.WriteLine($"[ERROR] {DateTime.Now}: {message}");
if (ex != null)
Console.WriteLine($"Exception: {ex.Message}");
}
}
public class ValidationService
{
public void ValidatePassword(string password)
{
if (string.IsNullOrEmpty(password) || password.Length < 8)
throw new ArgumentException("パスワードは8文字以上必要です");
Console.WriteLine("パスワードを検証しました");
}
public void ValidateUserData(UserData userData)
{
if (string.IsNullOrEmpty(userData.Name))
throw new ArgumentException("名前は必須です");
Console.WriteLine("ユーザーデータを検証しました");
}
}
// データモデル
public class UserData
{
public string Email { get; set; }
public string Name { get; set; }
public string Password { get; set; }
}
// ファサードクラス
public class UserRegistrationFacade
{
private readonly EmailService emailService;
private readonly UserDatabase userDatabase;
private readonly NotificationService notificationService;
private readonly Logger logger;
private readonly ValidationService validationService;
public UserRegistrationFacade()
{
emailService = new EmailService();
userDatabase = new UserDatabase();
notificationService = new NotificationService();
logger = new Logger();
validationService = new ValidationService();
}
public bool RegisterUser(UserData userData)
{
try
{
logger.LogInfo($"ユーザー登録開始: {userData.Email}");
// 各種バリデーション
validationService.ValidateUserData(userData);
validationService.ValidatePassword(userData.Password);
emailService.ValidateEmail(userData.Email);
// 重複チェック
if (userDatabase.UserExists(userData.Email))
{
logger.LogError("既に登録済みのメールアドレスです");
return false;
}
// ユーザーの保存
userDatabase.SaveUser(userData);
// 確認メールの送信
emailService.SendEmail(
userData.Email,
"ご登録ありがとうございます",
"アカウントが作成されました。"
);
// 管理者への通知
notificationService.NotifyAdmin($"新規ユーザー登録: {userData.Email}");
logger.LogInfo($"ユーザー登録完了: {userData.Email}");
return true;
}
catch (Exception ex)
{
logger.LogError("ユーザー登録中にエラーが発生しました", ex);
return false;
}
}
}
// より高度なファサードの例:非同期対応
public class AsyncUserRegistrationFacade
{
private readonly EmailService emailService;
private readonly UserDatabase userDatabase;
private readonly NotificationService notificationService;
private readonly Logger logger;
private readonly ValidationService validationService;
public AsyncUserRegistrationFacade()
{
emailService = new EmailService();
userDatabase = new UserDatabase();
notificationService = new NotificationService();
logger = new Logger();
validationService = new ValidationService();
}
public async Task<bool> RegisterUserAsync(UserData userData)
{
try
{
await Task.Run(() => logger.LogInfo($"ユーザー登録開始: {userData.Email}"));
// 並列でバリデーションを実行
await Task.WhenAll(
Task.Run(() => validationService.ValidateUserData(userData)),
Task.Run(() => validationService.ValidatePassword(userData.Password)),
Task.Run(() => emailService.ValidateEmail(userData.Email))
);
if (await Task.Run(() => userDatabase.UserExists(userData.Email)))
{
logger.LogError("既に登録済みのメールアドレスです");
return false;
}
// 並列でユーザー保存とメール送信を実行
await Task.WhenAll(
Task.Run(() => userDatabase.SaveUser(userData)),
Task.Run(() => emailService.SendEmail(
userData.Email,
"ご登録ありがとうございます",
"アカウントが作成されました。"
)),
Task.Run(() => notificationService.NotifyAdmin($"新規ユーザー登録: {userData.Email}"))
);
await Task.Run(() => logger.LogInfo($"ユーザー登録完了: {userData.Email}"));
return true;
}
catch (Exception ex)
{
await Task.Run(() => logger.LogError("ユーザー登録中にエラーが発生しました", ex));
return false;
}
}
}
// 使用例
class Program
{
static async Task Main()
{
// 同期バージョンの使用
Console.WriteLine("1. 同期的なユーザー登録:");
var facade = new UserRegistrationFacade();
var userData = new UserData
{
Email = "test@example.com",
Name = "Test User",
Password = "password123"
};
var result = facade.RegisterUser(userData);
Console.WriteLine($"登録結果: {result}\n");
// 非同期バージョンの使用
Console.WriteLine("2. 非同期のユーザー登録:");
var asyncFacade = new AsyncUserRegistrationFacade();
var asyncUserData = new UserData
{
Email = "async@example.com",
Name = "Async User",
Password = "password456"
};
var asyncResult = await asyncFacade.RegisterUserAsync(asyncUserData);
Console.WriteLine($"非同期登録結果: {asyncResult}");
}
}
Façadeパターンの詳細解説
目的
- 複雑なサブシステムに対する単純なインターフェースを提供する
- サブシステム間の依存関係を最小限に抑える
- クライアントの実装を簡素化する
適用すべき状況
複雑なサブシステムがある場合
- 多数のクラスの連携
- 複雑な初期化手順
- 複雑な処理フロー
サブシステムの使用を簡素化したい場合
- API の統合
- レガシーシステムのラッピング
- ライブラリの簡素化
依存関係を制御したい場合
- レイヤー間の分離
- モジュール間の結合度低減
- インターフェースの標準化
避けるべき状況
サブシステムが単純な場合
- 直接使用で十分
- 余分な抽象化層が不要
細かい制御が必要な場合
- 詳細な設定が必要
- カスタマイズが頻繁
パフォーマンスが重要な場合
- オーバーヘッドが問題
- 直接アクセスが必要
実装上の注意点
- インターフェース設計
public interface IUserRegistrationFacade
{
bool RegisterUser(UserData userData);
bool ValidateUser(UserData userData);
void NotifyUser(UserData userData);
}
- エラー処理
public class RobustFacade
{
public Result ExecuteOperation()
{
try
{
// 操作の実行
return Result.Success();
}
catch (Exception ex)
{
logger.LogError(ex);
return Result.Failure(ex.Message);
}
}
}
- 依存性注入
public class DependencyInjectedFacade
{
private readonly IEmailService emailService;
private readonly IUserDatabase userDatabase;
public DependencyInjectedFacade(
IEmailService emailService,
IUserDatabase userDatabase)
{
this.emailService = emailService;
this.userDatabase = userDatabase;
}
}
メリット
- サブシステムの複雑さを隠蔽
- 結合度の低減
- 使用の簡素化
- 一貫性のある操作の提供
デメリット
- ファサードの肥大化
- 柔軟性の制限
- パフォーマンスへの影響
- 追加の抽象化層
ベストプラクティス
- インターフェース分割
public interface IUserManagementFacade
{
bool RegisterUser(UserData userData);
}
public interface IUserValidationFacade
{
bool ValidateUser(UserData userData);
}
- 設定の柔軟性
public class ConfigurableFacade
{
private readonly FacadeConfiguration config;
public ConfigurableFacade(Action<FacadeConfiguration> configure)
{
config = new FacadeConfiguration();
configure(config);
}
}
- メソッドチェーン
public class FluentFacade
{
public FluentFacade ValidateUser()
{
// バリデーション処理
return this;
}
public FluentFacade SaveUser()
{
// 保存処理
return this;
}
}
これで23個のGoFデザインパターンの説明は全て完了しました。他に質問はありますか?
新旧の違い
例えば、オブジェクト指向では、昔は「継承」が推奨されていましたが、今は推奨されません。
このように、昔と今のデザインパターンに関する考え方が、変わっている部分はありますか。
はい、デザインパターンに関する考え方も時代とともに変化しています。主な変化をいくつか説明させていただきます:
- 継承から合成(コンポジション)へ
昔:継承を使って機能を拡張することが推奨されていた
今:「継承より合成」が原則となっている
理由:
- 継承は強い結合を生む
- 継承は柔軟性が低い
- 継承は「is-a」関係が必要
- 多重継承は複雑な問題を引き起こす
- 継承は実装の詳細が漏れやすい
- シングルトンパターンの評価
昔:広く使用が推奨されていた
今:使用を避けるべきパターンとして認識されている
理由:
- グローバル状態を導入する
- テストが困難
- 単一責任の原則に反する
- 依存性の注入が困難
- Observer パターン
昔:独自実装が一般的
今:イベント駆動やリアクティブプログラミング、フレームワークの機能を使用
理由:
- フレームワークの成熟
- 非同期処理の一般化
- メモリリークの防止が容易
- Factory パターン
昔:Simple Factory や Factory Method が一般的
今:依存性注入(DI)コンテナの使用が一般的
理由:
- フレームワークの発展
- テスタビリティの向上
- 設定の柔軟性
- Command パターン
昔:オブジェクトとして実装
今:関数型プログラミングの影響で、ラムダ式や関数オブジェクトとして実装
理由:
- 言語機能の進化
- より簡潔な実装が可能
- 関数型プログラミングの普及
- Template Method パターン
昔:継承ベースの実装
今:Strategy パターンや関数注入での実装が増加
理由:
- より柔軟な設計が可能
- テストが容易
- 動的な振る舞いの変更が可能
- Iterator パターン
昔:カスタム実装が必要
今:言語組み込みの機能を使用
理由:
- 言語サポートの充実
- LINQ などの高レベルな抽象化
- より簡潔で安全な実装
- 持続性と状態管理
昔:オブジェクト内での状態管理
今:イミュータブルなデータと純粋関数による処理
理由:
- 並行処理の増加
- 関数型プログラミングの影響
- デバッグの容易さ
- エラー処理
昔:例外処理
今:Result型やOption型の使用
理由:
- 型安全性の重視
- エラーフローの明確化
- パフォーマンスの考慮
- 非同期処理
昔:コールバックパターン
今:async/await パターン
理由:
- 言語サポートの向上
- 可読性の向上
- エラーハンドリングの改善
これらの変化の背景には、以下のような要因があります:
プログラミング言語の進化
- 言語機能の充実
- 型システムの強化
- 関数型プログラミングの影響
開発環境の変化
- マイクロサービスアーキテクチャ
- クラウドコンピューティング
- コンテナ化
新しい課題への対応
- スケーラビリティ
- 並行処理
- 分散システム
開発手法の変化
- アジャイル開発
- テスト駆動開発
- 継続的デリバリー
これらの変化を踏まえて、現代のソフトウェア開発では、より柔軟で保守性の高い設計が求められています。
デザインパターンについて、主要なパターンとC#での実装例を交えて説明させていただきます。
主要なデザインパターンについて、それぞれ説明していきます:
目的:クラスのインスタンスが1つだけ存在することを保証します
用途:設定管理、データベース接続、ログ記録など
特徴:
目的:オブジェクトの生成を専用のメソッドに委ねます
用途:オブジェクト生成のロジックを集中管理したい場合
特徴:
目的:オブジェクト間の1対多の依存関係を定義します
用途:イベント処理、状態変更の通知など
特徴:
目的:アルゴリズムを実行時に切り替え可能にします
用途:異なる計算方法、ソート方法の切り替えなど
特徴:
目的:オブジェクトに動的に機能を追加します
用途:UIコンポーネント、ストリーム処理など
特徴:
これらのパターンの使用例を示してみましょう:
これらのデザインパターンを適切に使用することで、以下のメリットが得られます:
具体的な実装について、さらに詳しく知りたい部分はありますか?