第 8 章: デザインパターンの適用¶
8.1 はじめに¶
前章では class と interface を使ってカプセル化とポリモーフィズムを実現しました。この章では デザインパターン を適用して、設計をさらに改善します。
8.2 Value Object パターン¶
FizzBuzzValue の強化¶
前章で作成した FizzBuzzValue は既に Value Object パターンを実現しています。
public class FizzBuzzValue : IEquatable<FizzBuzzValue>
{
public int Number { get; }
public string Value { get; }
public FizzBuzzValue(int number, string value)
{
Number = number;
Value = value;
}
public override string ToString() => $"{Number}:{Value}";
public bool Equals(FizzBuzzValue? other)
{
if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Number == other.Number && Value == other.Value;
}
public override bool Equals(object? obj) => Equals(obj as FizzBuzzValue);
public override int GetHashCode() => HashCode.Combine(Number, Value);
}
IEquatable<T> の実装により、2 つの FizzBuzzValue を値で比較できます。読み取り専用プロパティにより不変性が保証されます。Rust の #[derive(PartialEq, Clone)] に対し、C# では IEquatable<T> と GetHashCode を明示的に実装します。
8.3 First-Class Collection パターン¶
FizzBuzzList クラス¶
FizzBuzzValue のコレクションを専用の型でラップします。
namespace FizzBuzz.Domain.Model;
public class FizzBuzzList : IEquatable<FizzBuzzList>
{
private readonly List<FizzBuzzValue> _values;
public IReadOnlyList<FizzBuzzValue> Values => _values.AsReadOnly();
public FizzBuzzList()
{
_values = new List<FizzBuzzValue>();
}
public FizzBuzzList(List<FizzBuzzValue> values)
{
_values = new List<FizzBuzzValue>(values);
}
public FizzBuzzValue Get(int index) => _values[index];
public int Count => _values.Count;
public FizzBuzzList Add(FizzBuzzValue value)
{
var newValues = new List<FizzBuzzValue>(_values) { value };
return new FizzBuzzList(newValues);
}
public List<string> ToStringValues()
{
return _values.Select(v => v.Value).ToList();
}
// ...等価性の実装
}
First-Class Collection パターンにより、コレクション操作の責務を FizzBuzzList に集約できます。Add メソッドは新しいリストを返すイミュータブルな設計です。IReadOnlyList<T> で公開することで外部からの変更を防ぎます。
テストの作成¶
public class FizzBuzzListTest
{
[Fact]
public void 空のリストを作成できる()
{
var list = new FizzBuzzList();
Assert.Equal(0, list.Count);
}
[Fact]
public void 値を追加できる()
{
var list = new FizzBuzzList();
var newList = list.Add(new FizzBuzzValue(1, "1"));
Assert.Equal(1, newList.Count);
Assert.Equal(0, list.Count); // 元のリストは変更されない
}
[Fact]
public void 文字列リストに変換できる()
{
var list = new FizzBuzzList(new List<FizzBuzzValue>
{
new FizzBuzzValue(1, "1"),
new FizzBuzzValue(3, "Fizz"),
new FizzBuzzValue(5, "Buzz")
});
var strings = list.ToStringValues();
Assert.Equal(new List<string> { "1", "Fizz", "Buzz" }, strings);
}
}
8.4 Command パターン¶
IFizzBuzzCommand インターフェース¶
FizzBuzz の操作をコマンドとして抽象化します。
namespace FizzBuzz.Application;
using FizzBuzz.Domain.Model;
public interface IFizzBuzzCommand
{
FizzBuzzValue ExecuteValue(int number);
FizzBuzzList ExecuteList(int count);
}
単一値コマンド¶
public class FizzBuzzValueCommand : IFizzBuzzCommand
{
private readonly IFizzBuzzType _type;
public FizzBuzzValueCommand(IFizzBuzzType type)
{
_type = type;
}
public FizzBuzzValue ExecuteValue(int number)
{
return _type.Generate(number);
}
public FizzBuzzList ExecuteList(int count)
{
throw new NotSupportedException(
"FizzBuzzValueCommand does not support list execution.");
}
}
リストコマンド¶
public class FizzBuzzListCommand : IFizzBuzzCommand
{
private readonly IFizzBuzzType _type;
public FizzBuzzListCommand(IFizzBuzzType type)
{
_type = type;
}
public FizzBuzzValue ExecuteValue(int number)
{
throw new NotSupportedException(
"FizzBuzzListCommand does not support single value execution.");
}
public FizzBuzzList ExecuteList(int count)
{
var values = Enumerable.Range(1, count)
.Select(i => _type.Generate(i))
.ToList();
return new FizzBuzzList(values);
}
}
コンストラクタインジェクションで IFizzBuzzType を受け取ることで、具体的なタイプに依存しない設計を実現しています。
テストの作成¶
public class FizzBuzzCommandTest
{
[Fact]
public void ValueCommandで単一値を取得できる()
{
var command = new FizzBuzzValueCommand(
FizzBuzzTypeFactory.Create(FizzBuzzTypeName.Standard));
var result = command.ExecuteValue(3);
Assert.Equal("Fizz", result.Value);
}
[Fact]
public void ListCommandでリストを生成できる()
{
var command = new FizzBuzzListCommand(
FizzBuzzTypeFactory.Create(FizzBuzzTypeName.Standard));
var result = command.ExecuteList(100);
Assert.Equal(100, result.Count);
}
}
8.5 Strategy パターン¶
IFizzBuzzType インターフェースと 3 つの実装クラス(FizzBuzzType01, FizzBuzzType02, FizzBuzzType03)は、Strategy パターンを実現しています。コマンドのコンストラクタに異なる IFizzBuzzType を渡すことで、同じインターフェースで異なる振る舞いを切り替えられます。
Factory Method パターン¶
FizzBuzzTypeFactory.Create メソッドは Factory Method パターンです。
public static IFizzBuzzType Create(FizzBuzzTypeName name)
{
return Create((int)name);
}
FizzBuzzTypeName enum を使うことで、型安全なファクトリ呼び出しが可能です。
8.6 まとめ¶
この章では以下のデザインパターンを適用しました。
| パターン | 目的 | C# の実現方法 |
|---|---|---|
| Value Object | 値の等価性保証 | IEquatable<T> + 読み取り専用プロパティ |
| First-Class Collection | コレクション操作の集約 | class + List<T> + IReadOnlyList<T> |
| Command | 操作の抽象化と遅延実行 | interface + コンストラクタインジェクション |
| Strategy | アルゴリズムの交換可能性 | interface + 複数の実装クラス |
| Factory Method | タイプ番号による生成 | switch 式 + static メソッド |
次章では SOLID 原則の観点から設計を評価し、モジュール分割を行います。