Pythonで、デザインパターン「Facade」を学ぶ


GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

■ Facade(ファサード・パターン)

Facadeパターンあるいは Façadeパターン(ファサード・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義された、コンピュータソフトウェアのデザインパターンの1つである。Facade(ファサード)とは「建物の正面」を意味する。異なるサブシステムを単純な操作だけを持ったFacadeクラスで結び、サブシステム間の独立性を高める事を目的とする。

UML class and sequence diagram

UML class diagram


(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

Facadeパターンは、複雑に絡み合ってごちゃごちゃした詳細をまとめ、高レベルのインタフェース(API)を提供するそうです。Facade役は、システムの外側に対してはシンプルなインタフェースを見せるそうです。また、Facade役はシステム内側にある各クラスの役割を考えて、正しい順番でクラスを利用するそうです。

そういえば、昔のOpenStack Heat(オーケストレーション)の内部構造を調査していたときに、まさに、Facadeパターンに遭遇したのを思い出しました。
確か、 OpenStack heat-engine側では、SQLAlchemy経由でデータベースを利用していて、そのデータベース用セッションを、Facadeパターンで管理していたはずです。

heat/heat/db/sqlalchemy/api.py
def get_facade():
    global _facade

    if not _facade:
        _facade = db_session.EngineFacade.from_config(CONF)
        if CONF.profiler.enabled:
            if CONF.profiler.trace_sqlalchemy:
                osprofiler.sqlalchemy.add_tracing(sqlalchemy,
                                                  _facade.get_engine(),
                                                  "db")

    return _facade

■ "Facade"のサンプルプログラム

実際に、Facadeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

$ python Main.py 
welcome1.html is created for [email protected] (Hiroshi Yuki)
welcome2.html is created for [email protected] (Mamoru Takahashi)

サンプルプログラムを起動すると、2つのhtmlファイルが生成されます。
おのおのを、Webブラウザを確認してみるとこんな感じになりました。

  • welcome1.html
  • welcome2.html

■ サンプルプログラムの詳細

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Facade

  • ディレクトリ構成
.
├── Main.py
├── maildata.ini
└── pagemaker
    ├── __init__.py
    ├── database.py
    ├── html_writer.py
    └── page_maker.py

(1) Facade(正面)の役

Facade役は、システムを構成しているその他大勢の役の「シンプルな窓口」となります。Facade役は、高レベルでシンプルなインタフェースをシステム外部に提供します。
サンプルプログラムでは、PageMakerクラスが、この役を努めます。

pagemaker/page_maker.py
import sys
import json
from pagemaker.database import Database
from pagemaker.html_writer import HtmlWriter

class PageMaker(object):
    @classmethod
    def makeWelcomePage(cls, mailaddr, filename):
        try:
            prop = Database.getProperties('maildata')
            username =prop[mailaddr]
            writer = HtmlWriter(open(filename, 'w'))
            writer.title('Welcom to {}s page!'.format(username))
            writer.paragraph("We'll wait for your sending")
            writer.mailto(mailaddr, username)
            writer.close()
            print('{} is created for {} ({})'.format(filename, mailaddr, username))
        except Exception:
            print("# Failure occurred")

(2) システムを構成しているその他大勢の役

その他大勢の役は、それぞれの仕事を行いますが、Facade役のことは意識しません。Facade役から呼び出されて仕事を行いますが、その他大勢の役の方からFacade役を呼び出すことはありません。
サンプルプログラムでは、Databaseクラスと、HtmlWriterクラスが、この役を努めます。

pagemaker/database.py
from configparser import ConfigParser

class Database(object):
    @classmethod
    def getProperties(cls, dbname):
        filename = dbname + ".ini"
        conf = ConfigParser()
        try:
            conf.read(filename)
            return conf["MailAddress"]
        except Exception:
            print("Warning: [{0}] is not found.".format(filename))
maildata.init
[MailAddress]
hyuki@hyuki.com = Hiroshi Yuki
hanako@hyuki.com = Hananko Sato
tomura@hyuki.com = Tomura
mamoru@hyuki.com = Mamoru Takahashi
pagemaker/html_writer.py
class HtmlWriter(object):
    def __init__(self, writer):
        self.writer = writer

    def title(self, title):
        self.writer.write("<html>\n")
        self.writer.write("<head>")
        self.writer.write("<title>{0}</title>".format(title))
        self.writer.write("</head>\n")
        self.writer.write("<body>\n")
        self.writer.write("<h1>{0}</h1>\n".format(title))

    def paragraph(self, msg):
        self.writer.write("<p>{0}</p>\n".format(msg))

    def link(self, href, caption):
        self.writer.write("<a href=\"{0}\">{1}</a>".format(href, caption))

    def mailto(self, mailaddr, username):
        self.link("mailto:{0}".format(mailaddr), username)

    def close(self):
        self.writer.write("</body>\n")
        self.writer.write("</html>\n")
        self.writer.close()

(3) Client(依頼人)の役

Facadeパターンを利用する役です。
サンプルプログラムでは、startMainメソッドが、この役を努めます。

Main.py
from pagemaker.page_maker import PageMaker

def startMain():
    PageMaker.makeWelcomePage("[email protected]", "welcome1.html")
    PageMaker.makeWelcomePage("[email protected]", "welcome2.html")

if __name__ == '__main__':
    startMain()

■ 参考URL