python

【Python】区分オブジェクトをPythonの列挙型で表現する

最近【現場で役立つシステム設計の原則】という本を読み、今までがむしゃらに書いてきたコードを見直しています。

本はこちら

https://gihyo.jp/book/2017/978-4-7741-9087-7

その本の2章で記述されている区分オブジェクトをPythonで表現できるのか試してみました。

区分オブジェクトとは

区分オブジェクトは著書の中でこのように記述されています。

列挙型を使って、区分ごとのロジックをわかりやすく整理するこの方法を区分オブジェクトと呼びます。

区分定義を単なる定数ではなく、振る舞いを持ったオブジェクトとして表現します。

区分ごとのロジックとは多態のロジックを整理する際の1単位です。

著書では例として料金区分を上げて説明しています

  • 子供料金
  • 大人料金
  • シニア料金

これらを料金インタフェースから継承させることで振る舞いを統一することができます。

これを列挙型で定義することで同振る舞いをもたせたクラスを並べることができ、ロジックを整理できるということです。

これをPythonで記述できないか。試してみました。

Pythonで表現してみる

上記内容をPythonで表現すると以下のようになります。

今回は色区分で考えてみました。

from __future__ import annotations

from abc import ABC, abstractmethod
from enum import Enum


class Color(ABC):
    @abstractmethod
    def echo(self) -> None:
        pass

    @classmethod
    @abstractmethod
    def create(cls) -> Color:
        pass


class Red(Color):
    def echo(self) -> None:
        print("赤色です")

    @classmethod
    def create(cls) -> Red:
        return Red()


class Blue(Color):
    def echo(self) -> None:
        print("青色です")

    @classmethod
    def create(cls) -> Blue:
        return Blue()


class Yello(Color):
    def echo(self) -> None:
        print("黄色です")

    @classmethod
    def create(cls) -> Yello:
        return Yello()


class Colors(Enum):
    red = Red
    blue = Blue
    yello = Yello

    def create_color(self) -> Color:
        color: Color = self.value
        return color.create()


# 直接指定した場合
blue = Colors.blue.create_color()
blue.echo()

# これでもOK
red = Colors["red"].create_color()
red.echo()

一つずつ解説します。

ABC継承でインタフェースを定義

まずこの部分

from abc import ABC, abstractmethod

class Color(ABC):
    @abstractmethod
    def echo(self) -> None:
        pass

    @classmethod
    @abstractmethod
    def create(cls) -> Color:
        pass

ここでインタフェースを定義しています。

Pythonの場合abc.ABCを継承させることで抽象クラスを定義することが可能です。

また、メソッドに振る舞いを持たせる場合は@abstractmethodで定義することを促します。

区分クラスを作成

次は区分クラスを作成します。

class Red(Color):
    def echo(self) -> None:
        print("赤色です")

    @classmethod
    def create(cls) -> Red:
        return Red()


class Blue(Color):
    def echo(self) -> None:
        print("青色です")

    @classmethod
    def create(cls) -> Blue:
        return Blue()


class Yello(Color):
    def echo(self) -> None:
        print("黄色です")

    @classmethod
    def create(cls) -> Yello:
        return Yello()

ここで各区分クラスを定義しています。

インタフェースで定義したメソッドを実装することで各々の振る舞いを確定させています。

列挙型を定義

次に列挙型を定義します。

from enum import Enum

class Colors(Enum):
    red = Red
    blue = Blue
    yello = Yello

    def create_color(self) -> Color:
        color: Color = self.value
        return color.create()

ここで列挙型を決めます。

列挙型にはname = value を記述するのですが、valueにはstr, int型などの他にクラスを定義することも可能です。

そのため、valueに各区分クラスを定義することで振る舞いをまとめることが可能です。

また、列挙型を継承したクラス自身にもメソッドを定義することができます。

今回は選択した列挙型の区分クラスをインスタンス化して返却するcreate_color()を定義しました。

使用方法

最後に上記コードを実際に使用する例を記述しています

# 直接指定した場合
blue = Colors.blue.create_color()
blue.echo()

# これでもOK
red = Colors["red"].create_color()
red.echo()

# 実行結果
# 青色です
# 赤色です

この行で実際に使用しているサンプルを記述しています。

列挙型を使用するにはどちらかを使用します。

  • 列挙クラス.name
  • 列挙クラス[name]

上記で指定した後に列挙クラスで定義したcreate_color()をすることで多態インスタンスを生成できます。

結論: Pythonで区分オブジェクトはできる

Pythonを使って区分オブジェクトを表現できるか試してみました。

まとめです。

  • abc.ABCを使ってインタフェースを定義する
  • インタフェースを実装クラスで各々振る舞いを設定する
  • Enumで実装クラスをそれぞれ定義する

今回紹介した本には参考になる箇所がたくさんあったので逐一まとめていきたいと思います。

以上になります。