dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

『実践 Python 3』で学ぶデザインパターン : Abstract Factoryパターン

こちらの書籍でデザインパターンの勉強を始めました。

また、Java ですが家にこの本もあったので並行して読み進めていきます。

まだ最初のデザインパターンしか読んでませんが、2冊を比較してみて
「このデザインパターンは何か?どんなシチュエーションで利用すべきか? については
後者の書籍の方が説明が詳しいです。
(著者が結城浩さんということもあり理解しやすい日本語で書かれています)

『実践 Python 3』にはデザインパターンそのものの説明もありますが、
Python だとどう書くべきか」 という点にも重きを置いている印象です。

デザインパターン一覧

この順に勉強していく予定。


Abstract Factory パターンの概要

http://www.techscore.com/tech/DesignPattern/AbstractFactory.html/ の説明が一番しっくりきたので引用。

インスタンスの生成を専門に行うクラスを用意することで、整合性を必要とされる一連のオブジェクト群を間違いなく生成するためのパターンです。

たとえば、

  • あるオブジェクト(例:車)は複数の部品オブジェクト(例:タイヤ、ハンドル、etc.)から構成される
  • 部品オブジェクトはある種の "ファミリー" に属する(とみなせる)
    (例:車を作るためのタイヤやハンドルは車用のファミリーに属する。自転車を作るためのタイヤ、ハンドルはまた別のファミリーに属する)

...というとき、オブジェクト(車)の生成を担う専用の「ファクトリー」クラスを作ってしまい、必要な部品を組み合わせる処理はファクトリークラスに行わせる。


クラス図

書籍のサンプルコードのクラス図を記す。
(クラス図、PlantUML で作ったけどレイアウトの調整できなくてつらい)

f:id:dackdive:20160126094853p:plain

サンプルでは、「長方形やテキストを組み合わせて簡単な図形を描画し、結果をファイルに書き込む」ためのクラス群を紹介している。
2つのファクトリーがあり、1つはテキストで図形を描画、もう1つは SVG で図形を描画する。

テキストで描画するときと SVG で描画するときの個々のパーツは異なるので当然クラスも異なる。

テキストで描画するためのファクトリークラス DiagramFactory が抽象クラスとしての役割も兼ねており、SVG 描画用のファクトリークラス SvgDiagramFactory はこれを継承する。

書籍には

このパターンの名前に“abstract”という言葉が含まれているが、ひとつのクラスを、 インタフェースを提供する基底クラス(つまり、抽象クラス)と具象クラスの両方の機能 を担うクラスとして用いるのが普通である

とある。(そうなんだろうか?)

また、『増補改訂版Java言語で学ぶデザインパターン入門』では個々の部品クラスも抽象クラスを継承する関係になっていたが、サンプルコードではそうなっていない。


コード

https://github.com/zaki-yama/python-in-practice/tree/master/chapter-1/AbstractFactory


特徴

部品を組み合わせる部分の処理をファクトリークラスが担当することで、異なるファミリーの部品を間違って組み合わせるという間違いがなくなる
言い方を変えると、「異なるファミリーのクラスは混合できない」という制約が自動的にファクトリークラスに課される。

ファクトリークラスを追加することは簡単。
(Abstract なファクトリークラスのインターフェースに従って、必要な部品を用意すれば良い)

部品クラスを追加することは難しい。
(既にある具体的なファクトリークラスすべてに変更が生じることになる)


Python らしく書くためのコツ

共通のファミリーに属する部品クラスはそのファクトリークラスの内部クラスで定義する。
(利用する側にとっては部品クラスに直接アクセスする必要はなく、それぞれのファミリーのファクトリークラスにアクセスできれば良いので)

また、ファクトリークラスはインスタンス化する必要はないため、
それぞれの部品を生成するメソッドはクラスメソッドにする。

before
class DiagramFactory:

    def make_diagram(self, width, height):
        return Diagram(width, height)

    def make_rectangle(self, x, y, width, height, fill='white', stroke='black'):
        return Rectangle(x, y, width, height, fill, stroke)

    def make_text(self, x, y, text, fontsize=12):
        return Text(x, y, text, fontsize)


class SvgDiagramFactory(DiagramFactory):

    def make_diagram(self, width, height):
        return SvgDiagram(width, height)

    def make_rectangle(self, x, y, width, height, fill='white', stroke='black'):
        return SvgRectangle(x, y, width, height, fill, stroke)

    def make_text(self, x, y, text, fontsize=12):
        return SvgText(x, y, text, fontsize)


class Diagram:
    # ...

# ...Rectangle, Text クラス


class SvgDiagram:
    # ...

# ...SvgRectangle, SvgText クラス
after
class DiagramFactory:

    @classmethod
    def make_diagram(cls, width, height):
        return cls.Diagram(width, height)

    @classmethod
    def make_rectangle(cls, x, y, width, height, fill='white', stroke='black'):
        return cls.Rectangle(x, y, width, height, fill, stroke)

    @classmethod
    def make_text(cls, x, y, text, fontsize=12):
        return cls.Text(x, y, text, fontsize)

    # このファミリーでのみ使う定数の定義など

    class Diagram:
        # ...

    # Rectangle, Text クラス


class SvgDiagramFactory(DiagramFactory):
    # このファミリーでのみ使う定数の定義など

    class Diagram:
        # ...

    # Rectangle, Text クラス

before では具象クラスの各部品クラスに Svg*** という prefix をつける必要があったが
ファクトリーの内部クラスとしたことで抽象クラスと同じクラス名をつけることができるようになった。

また、それにより具象クラスで make_XXX 系のメソッドを実装する必要がなくなった。

例:

  • SvgDiagramFactory.make_rectangle() を呼ぶ
  • make_rectangle() は実装されてないので、基底クラス Diagrammake_rectangle() が呼ばれる
  • make_rectangle()Rectangle クラスを返すが、それは SvgDiagramFactory にも定義されてるので SvgDiagramFactory.Rectangle が返される