跳到主要内容

GoFデザインパターン - 振る舞いパターン Part 1

振る舞いパターンは、オブジェクト間の通信、責任の分担、アルゴリズムの抽象化に焦点を当てています。ここでは前半の6つのパターンを紹介します。

1. Chain of Responsibility(責任の連鎖)

概要

要求を処理できるオブジェクトの連鎖を作り、要求が処理されるまで順に渡していきます。送信側と受信側を分離します。

使用場面

  • 複数のオブジェクトが要求を処理する可能性がある場合
  • 処理順序を動的に変更したい場合
  • ログフィルタリング、認証フロー、例外処理

サンプルコード

// Handler(抽象クラス)
public abstract class Approver
{
protected Approver _successor;

public void SetSuccessor(Approver successor)
{
_successor = successor;
}

public abstract void ProcessRequest(Purchase purchase);
}

// Request
public class Purchase
{
public int Number { get; set; }
public double Amount { get; set; }
public string Purpose { get; set; }

public Purchase(int number, double amount, string purpose)
{
Number = number;
Amount = amount;
Purpose = purpose;
}
}

// Concrete Handlers
public class Director : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 10000.0)
{
Console.WriteLine($"{this.GetType().Name} approved request# {purchase.Number}");
}
else if (_successor != null)
{
_successor.ProcessRequest(purchase);
}
}
}

public class VicePresident : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 25000.0)
{
Console.WriteLine($"{this.GetType().Name} approved request# {purchase.Number}");
}
else if (_successor != null)
{
_successor.ProcessRequest(purchase);
}
}
}

public class President : Approver
{
public override void ProcessRequest(Purchase purchase)
{
if (purchase.Amount < 100000.0)
{
Console.WriteLine($"{this.GetType().Name} approved request# {purchase.Number}");
}
else
{
Console.WriteLine($"Request# {purchase.Number} requires an executive meeting!");
}
}
}

// より実践的な例:ロギングミドルウェア
public abstract class Logger
{
public static int INFO = 1;
public static int DEBUG = 2;
public static int ERROR = 3;

protected int _level;
protected Logger _nextLogger;

public void SetNextLogger(Logger nextLogger)
{
_nextLogger = nextLogger;
}

public void LogMessage(int level, string message)
{
if (_level <= level)
{
Write(message);
}

if (_nextLogger != null)
{
_nextLogger.LogMessage(level, message);
}
}

protected abstract void Write(string message);
}

public class ConsoleLogger : Logger
{
public ConsoleLogger(int level)
{
_level = level;
}

protected override void Write(string message)
{
Console.WriteLine($"Standard Console::Logger: {message}");
}
}

public class ErrorLogger : Logger
{
public ErrorLogger(int level)
{
_level = level;
}

protected override void Write(string message)
{
Console.WriteLine($"Error Console::Logger: {message}");
}
}

public class FileLogger : Logger
{
public FileLogger(int level)
{
_level = level;
}

protected override void Write(string message)
{
Console.WriteLine($"File::Logger: {message}");
}
}

// 使用例
class Program
{
static void Main()
{
// 承認フローの例
Console.WriteLine("=== Purchase Approval Chain ===\n");

Approver director = new Director();
Approver vp = new VicePresident();
Approver president = new President();

director.SetSuccessor(vp);
vp.SetSuccessor(president);

var p1 = new Purchase(2034, 350.00, "Supplies");
var p2 = new Purchase(2035, 32590.10, "Project X");
var p3 = new Purchase(2036, 122100.00, "New Branch Office");

director.ProcessRequest(p1);
director.ProcessRequest(p2);
director.ProcessRequest(p3);

// ロギングの例
Console.WriteLine("\n=== Logging Chain ===\n");

Logger errorLogger = new ErrorLogger(Logger.ERROR);
Logger fileLogger = new FileLogger(Logger.DEBUG);
Logger consoleLogger = new ConsoleLogger(Logger.INFO);

errorLogger.SetNextLogger(fileLogger);
fileLogger.SetNextLogger(consoleLogger);

Console.WriteLine("--- Sending INFO ---");
errorLogger.LogMessage(Logger.INFO, "This is an information.");

Console.WriteLine("\n--- Sending DEBUG ---");
errorLogger.LogMessage(Logger.DEBUG, "This is a debug level information.");

Console.WriteLine("\n--- Sending ERROR ---");
errorLogger.LogMessage(Logger.ERROR, "This is an error information.");
}
}

2. Command(コマンド)

概要

要求をオブジェクトとしてカプセル化し、パラメータ化されたクライアント、要求のキューイング、ログ記録、取り消し操作などをサポートします。

使用場面

  • GUIのボタンやメニューのアクション
  • トランザクション処理
  • マクロの記録と再生
  • Undo/Redo機能の実装

サンプルコード

// Commandインターフェース
public interface ICommand
{
void Execute();
void Undo();
}

// Receiver(実際の処理を行うクラス)
public class Light
{
public void TurnOn()
{
Console.WriteLine("The light is on");
}

public void TurnOff()
{
Console.WriteLine("The light is off");
}
}

// Concrete Commands
public class TurnOnLightCommand : ICommand
{
private Light _light;

public TurnOnLightCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOn();
}

public void Undo()
{
_light.TurnOff();
}
}

public class TurnOffLightCommand : ICommand
{
private Light _light;

public TurnOffLightCommand(Light light)
{
_light = light;
}

public void Execute()
{
_light.TurnOff();
}

public void Undo()
{
_light.TurnOn();
}
}

// Invoker(コマンドを実行するクラス)
public class RemoteControl
{
private ICommand _command;
private Stack<ICommand> _history = new Stack<ICommand>();

public void SetCommand(ICommand command)
{
_command = command;
}

public void PressButton()
{
_command.Execute();
_history.Push(_command);
}

public void PressUndo()
{
if (_history.Count > 0)
{
var command = _history.Pop();
command.Undo();
}
}
}

// より実践的な例:テキストエディタ
public class TextEditor
{
private string _text = "";

public void AddText(string text)
{
_text += text;
Console.WriteLine($"Current Text: \"{_text}\"");
}

public void RemoveText(int length)
{
if (length <= _text.Length)
{
_text = _text.Substring(0, _text.Length - length);
Console.WriteLine($"Current Text: \"{_text}\"");
}
}

public string GetText() => _text;
}

public class AddTextCommand : ICommand
{
private TextEditor _editor;
private string _textToAdd;

public AddTextCommand(TextEditor editor, string textToAdd)
{
_editor = editor;
_textToAdd = textToAdd;
}

public void Execute()
{
_editor.AddText(_textToAdd);
}

public void Undo()
{
_editor.RemoveText(_textToAdd.Length);
}
}

// 使用例
class Program
{
static void Main()
{
// リモコンの例
Console.WriteLine("=== Remote Control ===\n");

Light livingRoomLight = new Light();
ICommand lightOn = new TurnOnLightCommand(livingRoomLight);
ICommand lightOff = new TurnOffLightCommand(livingRoomLight);

RemoteControl remote = new RemoteControl();

remote.SetCommand(lightOn);
remote.PressButton(); // Light On

remote.SetCommand(lightOff);
remote.PressButton(); // Light Off

Console.WriteLine("Undo last action:");
remote.PressUndo(); // Undo Light Off -> Light On

// テキストエディタの例
Console.WriteLine("\n=== Text Editor with Undo ===\n");

TextEditor editor = new TextEditor();
RemoteControl editorRemote = new RemoteControl();

ICommand typeHello = new AddTextCommand(editor, "Hello ");
ICommand typeWorld = new AddTextCommand(editor, "World!");

editorRemote.SetCommand(typeHello);
editorRemote.PressButton();

editorRemote.SetCommand(typeWorld);
editorRemote.PressButton();

Console.WriteLine("\nUndo operations:");
editorRemote.PressUndo();
editorRemote.PressUndo();
}
}

3. Interpreter(インタプリタ)

概要

言語の文法表現を定義し、その表現を使用して文を解釈するインタプリタを構築します。

使用場面

  • SQLのようなクエリ言語の解析
  • 正規表現エンジン
  • 数式計算エンジン
  • 設定ファイルの解析

サンプルコード

// Context
public class Context
{
private Dictionary<string, int> _variables = new Dictionary<string, int>();

public void SetVariable(string name, int value)
{
_variables[name] = value;
}

public int GetVariable(string name)
{
if (_variables.ContainsKey(name))
return _variables[name];
return 0;
}
}

// Abstract Expression
public interface IExpression
{
int Interpret(Context context);
}

// Terminal Expression
public class NumberExpression : IExpression
{
private int _number;

public NumberExpression(int number)
{
_number = number;
}

public int Interpret(Context context)
{
return _number;
}
}

public class VariableExpression : IExpression
{
private string _name;

public VariableExpression(string name)
{
_name = name;
}

public int Interpret(Context context)
{
return context.GetVariable(_name);
}
}

// Non-terminal Expressions
public class AddExpression : IExpression
{
private IExpression _left;
private IExpression _right;

public AddExpression(IExpression left, IExpression right)
{
_left = left;
_right = right;
}

public int Interpret(Context context)
{
return _left.Interpret(context) + _right.Interpret(context);
}
}

public class SubtractExpression : IExpression
{
private IExpression _left;
private IExpression _right;

public SubtractExpression(IExpression left, IExpression right)
{
_left = left;
_right = right;
}

public int Interpret(Context context)
{
return _left.Interpret(context) - _right.Interpret(context);
}
}

// 使用例
class Program
{
static void Main()
{
// 式: x + y - z + 10
// x = 5, y = 10, z = 2

var context = new Context();
context.SetVariable("x", 5);
context.SetVariable("y", 10);
context.SetVariable("z", 2);

// 構文木を構築: ((x + y) - z) + 10
IExpression expression = new AddExpression(
new SubtractExpression(
new AddExpression(
new VariableExpression("x"),
new VariableExpression("y")
),
new VariableExpression("z")
),
new NumberExpression(10)
);

int result = expression.Interpret(context);
Console.WriteLine($"Result: {result}"); // (5 + 10 - 2) + 10 = 23
}
}

4. Iterator(イテレータ)

概要

集約オブジェクトの内部表現を公開せずに、その要素に順次アクセスする方法を提供します。

使用場面

  • コレクションの内部構造を隠蔽したい場合
  • 複数の異なる走査方法を提供したい場合
  • 異なる種類のコレクションに対して統一的なインターフェースを提供したい場合

サンプルコード

// .NETではIEnumerable<T>とIEnumerator<T>が標準で提供されていますが、
// パターンの理解のために独自実装を示します。

// Iterator Interface
public interface IIterator<T>
{
T First();
T Next();
bool IsDone { get; }
T CurrentItem { get; }
}

// Aggregate Interface
public interface IAggregate<T>
{
IIterator<T> CreateIterator();
}

// Concrete Aggregate
public class BookCollection : IAggregate<Book>
{
private List<Book> _books = new List<Book>();

public void AddBook(Book book)
{
_books.Add(book);
}

public int Count => _books.Count;

public Book this[int index] => _books[index];

public IIterator<Book> CreateIterator()
{
return new BookIterator(this);
}
}

public class Book
{
public string Title { get; set; }
public string Author { get; set; }

public Book(string title, string author)
{
Title = title;
Author = author;
}

public override string ToString() => $"{Title} by {Author}";
}

// Concrete Iterator
public class BookIterator : IIterator<Book>
{
private BookCollection _collection;
private int _current = 0;

public BookIterator(BookCollection collection)
{
_collection = collection;
}

public Book First()
{
_current = 0;
return _collection.Count > 0 ? _collection[_current] : null;
}

public Book Next()
{
_current++;
if (!IsDone)
return _collection[_current];
else
return null;
}

public bool IsDone => _current >= _collection.Count;

public Book CurrentItem => _collection[_current];
}

// .NET標準のIEnumerableを使用した例(推奨)
public class ModernBookCollection : IEnumerable<Book>
{
private List<Book> _books = new List<Book>();

public void Add(Book book) => _books.Add(book);

public IEnumerator<Book> GetEnumerator()
{
// yield returnを使用するとコンパイラがイテレータを自動生成します
foreach (var book in _books)
{
yield return book;
}
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

// 使用例
class Program
{
static void Main()
{
// GoFパターンの実装を使用
Console.WriteLine("=== Custom Iterator ===\n");

var library = new BookCollection();
library.AddBook(new Book("Design Patterns", "GoF"));
library.AddBook(new Book("Clean Code", "Robert C. Martin"));
library.AddBook(new Book("Refactoring", "Martin Fowler"));

var iterator = library.CreateIterator();

Console.WriteLine("Iterating over books:");
for (var item = iterator.First(); !iterator.IsDone; item = iterator.Next())
{
Console.WriteLine(item);
}

// .NET標準の実装を使用
Console.WriteLine("\n=== .NET IEnumerable ===\n");

var modernLibrary = new ModernBookCollection
{
new Book("The Pragmatic Programmer", "Andrew Hunt"),
new Book("Domain-Driven Design", "Eric Evans")
};

foreach (var book in modernLibrary)
{
Console.WriteLine(book);
}
}
}

5. Mediator(メディエーター)

概要

オブジェクト群の相互作用をカプセル化するオブジェクトを定義します。オブジェクト同士が直接参照し合うのを防ぎ、結合度を下げます。

使用場面

  • 多数のオブジェクトが複雑に通信し合っている場合(チャットルームなど)
  • GUIコンポーネント間の連携(ボタンを押すとテキストボックスがクリアされるなど)
  • 航空管制システム

サンプルコード

// Mediator Interface
public interface IChatMediator
{
void SendMessage(string message, User user);
void AddUser(User user);
}

// Colleague Class
public abstract class User
{
protected IChatMediator _mediator;
public string Name { get; }

public User(IChatMediator mediator, string name)
{
_mediator = mediator;
Name = name;
}

public abstract void Send(string message);
public abstract void Receive(string message);
}

// Concrete Mediator
public class ChatRoom : IChatMediator
{
private List<User> _users = new List<User>();

public void AddUser(User user)
{
_users.Add(user);
}

public void SendMessage(string message, User sender)
{
foreach (var user in _users)
{
// 送信者以外にメッセージを送る
if (user != sender)
{
user.Receive(message);
}
}
}
}

// Concrete Colleagues
public class ConcreteUser : User
{
public ConcreteUser(IChatMediator mediator, string name) : base(mediator, name) { }

public override void Send(string message)
{
Console.WriteLine($"{Name} sends: {message}");
_mediator.SendMessage(message, this);
}

public override void Receive(string message)
{
Console.WriteLine($"{Name} receives: {message}");
}
}

// GUIコンポーネントの連携例
public interface IDialogMediator
{
void Notify(Component sender, string eventCode);
}

public abstract class Component
{
protected IDialogMediator _mediator;

public Component(IDialogMediator mediator)
{
_mediator = mediator;
}

public void Click()
{
_mediator.Notify(this, "click");
}

public void KeyPress()
{
_mediator.Notify(this, "keypress");
}
}

public class Button : Component
{
public Button(IDialogMediator mediator) : base(mediator) { }
}

public class TextBox : Component
{
public TextBox(IDialogMediator mediator) : base(mediator) { }

public void Clear()
{
Console.WriteLine("TextBox cleared.");
}
}

public class CheckBox : Component
{
public bool IsChecked { get; set; }
public CheckBox(IDialogMediator mediator) : base(mediator) { }
}

public class RegistrationDialog : IDialogMediator
{
public Button SubmitButton { get; set; }
public Button CancelButton { get; set; }
public TextBox UsernameBox { get; set; }
public CheckBox TermsCheckBox { get; set; }

public void Notify(Component sender, string eventCode)
{
if (sender == SubmitButton && eventCode == "click")
{
if (TermsCheckBox.IsChecked)
Console.WriteLine("Submitting registration...");
else
Console.WriteLine("Please accept terms.");
}
else if (sender == CancelButton && eventCode == "click")
{
UsernameBox.Clear();
Console.WriteLine("Registration cancelled.");
}
else if (sender == TermsCheckBox && eventCode == "click")
{
Console.WriteLine($"Terms accepted: {TermsCheckBox.IsChecked}");
}
}
}

// 使用例
class Program
{
static void Main()
{
// チャットルームの例
Console.WriteLine("=== Chat Room ===\n");

IChatMediator chatRoom = new ChatRoom();

User user1 = new ConcreteUser(chatRoom, "Alice");
User user2 = new ConcreteUser(chatRoom, "Bob");
User user3 = new ConcreteUser(chatRoom, "Charlie");

chatRoom.AddUser(user1);
chatRoom.AddUser(user2);
chatRoom.AddUser(user3);

user1.Send("Hello everyone!");

// GUI連携の例
Console.WriteLine("\n=== GUI Dialog ===\n");

var dialog = new RegistrationDialog();

// コンポーネントの初期化(相互参照はMediatorが管理)
dialog.SubmitButton = new Button(dialog);
dialog.CancelButton = new Button(dialog);
dialog.UsernameBox = new TextBox(dialog);
dialog.TermsCheckBox = new CheckBox(dialog);

Console.WriteLine("User clicks Submit without checking terms:");
dialog.SubmitButton.Click();

Console.WriteLine("\nUser checks terms:");
dialog.TermsCheckBox.IsChecked = true;
dialog.TermsCheckBox.Click();

Console.WriteLine("\nUser clicks Submit again:");
dialog.SubmitButton.Click();

Console.WriteLine("\nUser clicks Cancel:");
dialog.CancelButton.Click();
}
}

6. Memento(メメント)

概要

カプセル化を破壊せずに、オブジェクトの内部状態を保存し、後でその状態に戻せるようにします。

使用場面

  • テキストエディタのスナップショット
  • ゲームのセーブポイント
  • トランザクションのロールバック

サンプルコード

// Originator(状態を持つオブジェクト)
public class GameCharacter
{
public int Level { get; set; }
public int Health { get; set; }
public string Location { get; set; }

public void DisplayState()
{
Console.WriteLine($"Level: {Level}, Health: {Health}, Location: {Location}");
}

// 状態を保存
public Memento Save()
{
Console.WriteLine("Saving game state...");
return new Memento(Level, Health, Location);
}

// 状態を復元
public void Restore(Memento memento)
{
Console.WriteLine("Restoring game state...");
Level = memento.Level;
Health = memento.Health;
Location = memento.Location;
}
}

// Memento(状態を保持するオブジェクト)
public class Memento
{
public int Level { get; }
public int Health { get; }
public string Location { get; }

public Memento(int level, int health, string location)
{
Level = level;
Health = health;
Location = location;
}
}

// Caretaker(Mementoを管理するオブジェクト)
public class GameHistory
{
private Stack<Memento> _history = new Stack<Memento>();

public void Save(GameCharacter character)
{
_history.Push(character.Save());
}

public void Undo(GameCharacter character)
{
if (_history.Count > 0)
{
character.Restore(_history.Pop());
}
else
{
Console.WriteLine("No history to undo.");
}
}
}

// 使用例
class Program
{
static void Main()
{
var player = new GameCharacter { Level = 1, Health = 100, Location = "Start Village" };
var history = new GameHistory();

player.DisplayState();

// セーブポイント1
history.Save(player);

// ゲーム進行
player.Level = 2;
player.Health = 90;
player.Location = "Dark Forest";
player.DisplayState();

// セーブポイント2
history.Save(player);

// ボス戦でダメージ
player.Health = 10;
player.Location = "Boss Room";
player.DisplayState();

// 死亡したのでロード(Undo)
Console.WriteLine("\n--- Player Died! Loading last save... ---");
history.Undo(player);
player.DisplayState();

// さらに戻る
Console.WriteLine("\n--- Loading previous save... ---");
history.Undo(player);
player.DisplayState();
}
}

まとめ

振る舞いパターン(Part 1)の比較:

パターン主な目的使用場面
Chain of Responsibility要求を順次処理承認フロー、ロギング
Command要求のカプセル化Undo/Redo、マクロ、キュー
Interpreter言語の解釈クエリ解析、数式計算
Iterator要素への順次アクセスコレクション走査
Mediatorオブジェクト間の調整チャット、GUI連携
Memento状態の保存と復元スナップショット、Undo

選択のガイドライン

  • 処理者を動的に決定したい → Chain of Responsibility
  • 操作をオブジェクトとして扱いたい → Command
  • 独自の言語や文法を扱いたい → Interpreter
  • コレクションの内部構造を隠したい → Iterator
  • 複雑な相互作用を整理したい → Mediator
  • オブジェクトの状態を保存・復元したい → Memento