5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

『独習デザインパターン』をPythonに読み替える Abstract Factory パターン

5
Posted at

Java から Python へ翻訳しよう

『独習〜』は Java で書いてあります。
Java は Java なのでそのまま Python にコードを書き換えるのは難しい部分が私にはあります。
書き換えるのが難しいと思ったところで、『あの言語でできることがこの言語ではできない』
ということは、難易度の差こそあれそんな場面はあんまりないと聞きます。
聞く機会がなかっただけかもしれんけど。

「デザインパターンってなんぞ?」というのを飲み込んでいこうとしていますが、月刊ペースだと先が思いやられるのでテンポ良く進みたいですね。
今回は Abstract Factory パターンをPythonに書き直していきます。

Abstract Factory とは

具象クラスを明確にせず、関連するオブジェクト群を生成するパターン。
OS毎の違いを吸収するのに使ったりするみたい。

アブストラクトファクトリを適用しない場合

useless_abstract_factory.py
# ! /usr/bin/env python3
from enum import Enum, auto
from typing import Union


class GuitarType(Enum):
    Telecaster: int = auto()
    LesPaul: int = auto()


# Telecaster の材をクラス化する
class TelecasterNeck:

    def feature(self) -> str:
        return "Maple, C Shape"


class TelecasterFingerboard:
    def feature(self) -> str:
        return "Maple, 7.25\" R"


class TelecasterBody:
    def feature(self) -> str:
        return "Swamp Ash"


# Les Paul の材をクラス化する
class LesPaulNeck:

    def feature(self) -> str:
        return "Mahogany, Slim taper shape"


class LesPaulFingerboard:
    def feature(self) -> str:
        return "Rosewood 12\" R"


class LesPaulBody:
    def feature(self) -> str:
        return "Mahogany and Maple"


class GuitarFactory:
    """Factory クラスから材を呼び出せるようにする"""

    # Telecaster の材を呼び出す
    def choice_telecaster_neck(self) -> TelecasterNeck:
        return TelecasterNeck()

    def choice_telecaster_fingerboard(self) -> TelecasterFingerboard:
        return TelecasterFingerboard()

    def choice_telecaster_body(self) -> TelecasterBody:
        return TelecasterBody()

    # Les Paul の材を呼び出す
    def choice_les_paul_neck(self) -> LesPaulNeck:
        return LesPaulNeck()

    def choice_les_paul_fingerboard(self) -> LesPaulFingerboard:
        return LesPaulFingerboard()

    def choice_les_paul_body(self) -> LesPaulBody:
        return LesPaulBody()


class Order:
    """Client 側のクラス"""

    def __init__(self, type: GuitarType) -> None:
        self.type: Enum = type

    def create_guitar(self) -> str:
        """
        オーダーに合わせて Fender か Gibson タイプを組み立てる
        Returns:
            str: 完成品の構成材

        """
        neck: Union[TelecasterNeck, LesPaulNeck]
        fingerboard: Union[TelecasterFingerboard, LesPaulFingerboard]
        body: Union[TelecasterBody, LesPaulBody]

        factory: GuitarFactory = GuitarFactory()

        if self.type == GuitarType.Telecaster:
            neck = factory.choice_telecaster_neck()
            fingerboard = factory.choice_telecaster_fingerboard()
            body = factory.choice_telecaster_body()

        if self.type == GuitarType.LesPaul:
            neck = factory.choice_les_paul_neck()
            fingerboard = factory.choice_les_paul_fingerboard()
            body = factory.choice_les_paul_body()

        # return で早めに返したいが、共通部分を括りたい気持ちも強い
        use_neck: str = neck.feature()
        use_fingerboard: str = fingerboard.feature()
        use_body: str = body.feature()

        return (f"{self.type.name} のネックは {use_neck}" +
                f"フィンガーボードは {use_fingerboard}" + f"ボディは {use_body} でできています。")


if __name__ == '__main__':
    order = Order(GuitarType.Telecaster)
    print(order.create_guitar())

Order.create_guitar() の長さをどうにかしたいですね。
それはさておき、このコードでは Stratcaster や SG、PRS などを追加してくれと言われると GuitarFactory クラスがどんどん肥大化していまいます。
各ギターの構成要素は決まっているため、新規商品を追加することを考えて種類を簡単に増やす仕組みに実装したいと考えます。

アブストラクトファクトリを適用した場合

abstract_factory.py
# ! /usr/bin/env python3
from enum import Enum, auto
from typing import Union


class GuitarType(Enum):
    Telecaster: int = auto()
    LesPaul: int = auto()


class Neck:

    def feature(self) -> str:
        pass


class Fingerboard:

    def feature(self) -> str:
        pass


class Body:

    def feature(self) -> str:
        pass


# Telecaster の材をクラス化する
class TelecasterNeck(Neck):

    def feature(self) -> str:
        return "Maple, C Shape"


class TelecasterFingerboard(Fingerboard):
    def feature(self) -> str:
        return "Maple, 7.25\" R"


class TelecasterBody(Body):
    def feature(self) -> str:
        return "Swamp Ash"


# Les Paul の材をクラス化する
class LesPaulNeck(Neck):

    def feature(self) -> str:
        return "Mahogany, Slim taper shape"


class LesPaulFingerboard(Fingerboard):
    def feature(self) -> str:
        return "Rosewood 12\" R"


class LesPaulBody(Body):
    def feature(self) -> str:
        return "Mahogany and Maple"


class GuitarFactory:

    def choice_neck(self) -> Union[TelecasterNeck, LesPaulNeck]:
        pass

    def choice_fingerboard(
            self) -> Union[TelecasterFingerboard, LesPaulFingerboard]:
        pass

    def choice_body(self) -> Union[TelecasterBody, LesPaulBody]:
        pass


class TelecasterFactory(GuitarFactory):

    def choice_neck(self) -> TelecasterNeck:
        return TelecasterNeck()

    def choice_fingerboard(self) -> TelecasterFingerboard:
        return TelecasterFingerboard()

    def choice_body(self) -> TelecasterBody:
        return TelecasterBody()


class LesPaulFactory(GuitarFactory):

    def choice_neck(self) -> LesPaulNeck:
        return LesPaulNeck()

    def choice_fingerboard(self) -> LesPaulFingerboard:
        return LesPaulFingerboard()

    def choice_body(self) -> LesPaulBody:
        return LesPaulBody()


class Order(GuitarFactory):

    def __init__(self, factory):
        factory: Union[TelecasterFactory, LesPaulFactory]

        if factory == GuitarType.Telecaster:
            factory = TelecasterFactory()
        elif factory == GuitarType.LesPaul:
            factory = LesPaulFactory()

        self.neck: Union[TelecasterNeck, LesPaulNeck]
        self.fingerboard: Union[TelecasterFingerboard, LesPaulFingerboard]
        self.body: Union[TelecasterBody, LesPaulBody]

        self.neck = factory.choice_neck()
        self.fingerboard = factory.choice_fingerboard()
        self.body = factory.choice_body()

    def make_guitar(self):
        return self.neck, self.fingerboard, self.body


if __name__ == '__main__':
    # ギターの種類を Order クラスの責任で指定
    guitar: GuitarType = GuitarType.Telecaster
    order: Order = Order(guitar)

    neck: str = order.neck.feature()
    fingerboard: str = order.fingerboard.feature()
    body: str = order.body.feature()
    print(f"{guitar.name}のネックは{neck}" +
          f"フィンガーボードは{fingerboard}" +
          f"ボディは{body}でできています。")

アブストラクトファクトリを適用すると、 GuitarFactoryクラスとその部品を抽象化でき、クライアント側からは具体的な処理が隠蔽されます。
そのため、クライアント側はどのようなものでも同じような対応ができるという利点があります。

また、部品をグループ化して生成処理をまとめることができるため、同時に使用するべきではない部品を組み合わせて使うことが少なくなります。

アブストラクトファクトリの弱点

ただし、要素を追加することが困難。
今回の場合はギターのネック・指板・ボディの3種類の要素で構成されています。
たとえばここにピックアップやペグを追加しようとすると、 GuitarFactory クラスに PickUpsPegs を追加しなければいけません。
そして、そのサブクラスにも上記の修正を加えていかなければなりません。

以上の理由で、新たな要素を追加すると非常に面倒なため、このパターンを適用するときは元から要素が少ない、またはほとんど増えない見込みのときに適用する方が無難なようで。

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?