Skip to content

第 7 章: カプセル化とポリモーフィズム

7.1 はじめに

第 1〜2 部で FizzBuzz の基本実装と開発環境を整えました。第 3 部では、手続き的なコードをオブジェクト指向設計にリファクタリングしていきます。この章では、FizzBuzz に 追加仕様 を導入し、カプセル化ポリモーフィズム を適用します。

7.2 追加仕様

FizzBuzz プログラムに以下の 3 つのタイプを追加します。

タイプ 動作
タイプ 1 通常の FizzBuzz(3 の倍数→Fizz、5 の倍数→Buzz、両方→FizzBuzz)
タイプ 2 数をそのまま文字列で返す
タイプ 3 3 と 5 の倍数のときだけ FizzBuzz、それ以外は数を返す

TODO リスト:

  • タイプ 1: 通常の FizzBuzz
  • タイプ 2: 数をそのまま返す
  • タイプ 3: FizzBuzz のみ
  • それ以外のタイプは例外を送出する

7.3 手続き的アプローチ

まず、テストから書きます。タイプをコンストラクタで受け取る設計にします。

class TestFizzBuzz:
    class TestType1:
        def setup_method(self):
            self.fizzbuzz = FizzBuzz(1)

        def test_3と5の倍数の場合FizzBuzzを返す(self):
            assert self.fizzbuzz.generate(15) == "FizzBuzz"

        def test_3の倍数の場合Fizzを返す(self):
            assert self.fizzbuzz.generate(3) == "Fizz"

        def test_5の倍数の場合Buzzを返す(self):
            assert self.fizzbuzz.generate(5) == "Buzz"

        def test_その他の場合数を文字列で返す(self):
            assert self.fizzbuzz.generate(1) == "1"

    class TestType2:
        def setup_method(self):
            self.fizzbuzz = FizzBuzz(2)

        def test_数をそのまま文字列で返す(self):
            assert self.fizzbuzz.generate(3) == "3"

    class TestType3:
        def setup_method(self):
            self.fizzbuzz = FizzBuzz(3)

        def test_3と5の倍数の場合FizzBuzzを返す(self):
            assert self.fizzbuzz.generate(15) == "FizzBuzz"

        def test_その他の場合数を文字列で返す(self):
            assert self.fizzbuzz.generate(3) == "3"

手続き的な実装は if/elif の連鎖になります。

class FizzBuzz:
    def __init__(self, type_: int) -> None:
        self._type = type_

    def generate(self, number: int) -> str:
        if self._type == 1:
            if number % 15 == 0:
                return "FizzBuzz"
            if number % 3 == 0:
                return "Fizz"
            if number % 5 == 0:
                return "Buzz"
            return str(number)
        elif self._type == 2:
            return str(number)
        elif self._type == 3:
            if number % 15 == 0:
                return "FizzBuzz"
            return str(number)
        else:
            raise ValueError("未定義のタイプです")

このコードには問題があります。タイプが増えるたびに if/elif が肥大化し、変更の影響範囲が広がります。

7.4 カプセル化

@property によるフィールドアクセス制御

Python では @property デコレータを使ってフィールドのアクセスを制御します。setter を定義しないことで 読み取り専用 にできます。

class FizzBuzz:
    def __init__(self, type_: int) -> None:
        self._type = type_

    @property
    def type(self) -> int:
        return self._type

Java の private final + getter に相当します。外部から fizzbuzz.type で読み取れますが、fizzbuzz.type = 2 のような代入はエラーになります。

7.5 ポリモーフィズム

抽象基底クラスの導入

if/elif の連鎖をポリモーフィズムで置き換えます。Python では abc.ABC@abstractmethod を使います。

from abc import ABC, abstractmethod

class FizzBuzzType(ABC):
    @abstractmethod
    def generate(self, number: int) -> str:
        pass

    def _is_fizz(self, number: int) -> bool:
        return number % 3 == 0

    def _is_buzz(self, number: int) -> bool:
        return number % 5 == 0

    def _is_fizz_buzz(self, number: int) -> bool:
        return number % 15 == 0

具象クラスの作成

class FizzBuzzType01(FizzBuzzType):
    def generate(self, number: int) -> str:
        if self._is_fizz_buzz(number):
            return "FizzBuzz"
        if self._is_fizz(number):
            return "Fizz"
        if self._is_buzz(number):
            return "Buzz"
        return str(number)

class FizzBuzzType02(FizzBuzzType):
    def generate(self, number: int) -> str:
        return str(number)

class FizzBuzzType03(FizzBuzzType):
    def generate(self, number: int) -> str:
        if self._is_fizz_buzz(number):
            return "FizzBuzz"
        return str(number)

ファクトリメソッド

タイプの生成を一箇所にまとめます。

class FizzBuzzType(ABC):
    @staticmethod
    def create(type_: int) -> "FizzBuzzType":
        if type_ == 1:
            return FizzBuzzType01()
        elif type_ == 2:
            return FizzBuzzType02()
        elif type_ == 3:
            return FizzBuzzType03()
        else:
            raise ValueError("未定義のタイプです")

7.6 テストの更新

リファクタリング後のテストは、ファクトリメソッド経由でタイプを生成します。

class TestFizzBuzzType:
    class TestType01:
        def setup_method(self):
            self.type = FizzBuzzType.create(1)

        def test_FizzBuzzを返す(self):
            assert self.type.generate(15) == "FizzBuzz"

        def test_Fizzを返す(self):
            assert self.type.generate(3) == "Fizz"

        def test_Buzzを返す(self):
            assert self.type.generate(5) == "Buzz"

        def test_数を文字列で返す(self):
            assert self.type.generate(1) == "1"

7.7 まとめ

概念 Java Python
フィールド隠蔽 private final _ 接頭辞 + @property
抽象クラス abstract class abc.ABC + @abstractmethod
ファクトリメソッド static メソッド @staticmethod
条件分岐の排除 ポリモーフィズム ポリモーフィズム

次の章では、Value Object や Command パターンなどのデザインパターンを適用します。