第 8 章: デザインパターンの適用¶
8.1 はじめに¶
前章では、カプセル化とポリモーフィズムで if/elif の連鎖を排除しました。この章では、さらに デザインパターン を適用して設計を洗練させます。
適用するパターン:
- Value Object — 値をオブジェクトとして表現
- First-Class Collection — コレクションをドメインオブジェクト化
- Command パターン — 操作をオブジェクト化
- Null Object パターン — 例外の代わりにデフォルト動作を提供
8.2 Value Object(値オブジェクト)¶
FizzBuzz の結果を「数」と「変換後の文字列」のペアとして保持する値オブジェクトを作成します。
class FizzBuzzValue:
def __init__(self, number: int, value: str) -> None:
self._number = number
self._value = value
@property
def number(self) -> int:
return self._number
@property
def value(self) -> str:
return self._value
def __eq__(self, other: object) -> bool:
if not isinstance(other, FizzBuzzValue):
return NotImplemented
return self._number == other._number and self._value == other._value
def __hash__(self) -> int:
return hash((self._number, self._value))
def __repr__(self) -> str:
return f"{self._number}:{self._value}"
テスト¶
class TestFizzBuzzValue:
def test_値を保持する(self):
value = FizzBuzzValue(1, "1")
assert value.number == 1
assert value.value == "1"
def test_同じ値は等しい(self):
assert FizzBuzzValue(1, "1") == FizzBuzzValue(1, "1")
def test_文字列表現(self):
assert repr(FizzBuzzValue(3, "Fizz")) == "3:Fizz"
FizzBuzzType の更新¶
generate の戻り値を str から FizzBuzzValue に変更します。
class FizzBuzzType01(FizzBuzzType):
def generate(self, number: int) -> FizzBuzzValue:
if self._is_fizz_buzz(number):
return FizzBuzzValue(number, "FizzBuzz")
if self._is_fizz(number):
return FizzBuzzValue(number, "Fizz")
if self._is_buzz(number):
return FizzBuzzValue(number, "Buzz")
return FizzBuzzValue(number, str(number))
8.3 First-Class Collection¶
FizzBuzz の結果リストを専用クラスで包みます。
class FizzBuzzList:
def __init__(self, values: list[FizzBuzzValue] | None = None) -> None:
self._values: list[FizzBuzzValue] = list(values) if values else []
def add(self, value: FizzBuzzValue) -> None:
self._values.append(value)
def get(self, index: int) -> FizzBuzzValue:
return self._values[index]
def size(self) -> int:
return len(self._values)
def __eq__(self, other: object) -> bool:
if not isinstance(other, FizzBuzzList):
return NotImplemented
return self._values == other._values
def __hash__(self) -> int:
return hash(tuple(self._values))
def __repr__(self) -> str:
return "[" + ", ".join(repr(v) for v in self._values) + "]"
8.4 Command パターン¶
操作をオブジェクトとしてカプセル化します。
from abc import ABC, abstractmethod
class FizzBuzzCommand(ABC):
@abstractmethod
def execute(self) -> FizzBuzzValue | FizzBuzzList:
pass
class FizzBuzzValueCommand(FizzBuzzCommand):
def __init__(self, type_: FizzBuzzType, number: int) -> None:
self._type = type_
self._number = number
def execute(self) -> FizzBuzzValue:
return self._type.generate(self._number)
class FizzBuzzListCommand(FizzBuzzCommand):
MAX_NUMBER: int = 100
def __init__(self, type_: FizzBuzzType) -> None:
self._type = type_
def execute(self) -> FizzBuzzList:
result = FizzBuzzList()
for i in range(1, self.MAX_NUMBER + 1):
result.add(self._type.generate(i))
return result
8.5 Null Object パターン¶
未定義のタイプに対して例外ではなくデフォルト動作を返します。
class FizzBuzzTypeNotDefined(FizzBuzzType):
def generate(self, number: int) -> FizzBuzzValue:
return FizzBuzzValue(number, "")
ファクトリメソッドを更新します。
@staticmethod
def create(type_: int) -> "FizzBuzzType":
types = {
1: FizzBuzzType01,
2: FizzBuzzType02,
3: FizzBuzzType03,
}
return types.get(type_, FizzBuzzTypeNotDefined)()
8.6 まとめ¶
| パターン | クラス | 効果 |
|---|---|---|
| Value Object | FizzBuzzValue |
値の意味を明示し、等価性を保証 |
| First-Class Collection | FizzBuzzList |
コレクション操作を一元化 |
| Command | FizzBuzzValueCommand / FizzBuzzListCommand |
操作と実行を分離 |
| Null Object | FizzBuzzTypeNotDefined |
例外の代わりにデフォルト動作 |
| Factory Method | FizzBuzzType.create() |
生成ロジックの一元化 |
次の章では、SOLID 原則を確認しながらモジュール設計を行います。