Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Template Method
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Facade パターンの使われる場面◆◆

Facade ファサードは仏語で建物の正面という意味です。Facade パターンは,複雑な手順の作業を統一した手段でできるようにいわば「入口(窓口)を1つにする」ということを可能にします。

また,Facade みかけ・外見を統一すると解釈することもできます。


(2023-11-18)Python3.11で動作確認済み

正常に動作して出力も正常なのだが,エラーではない警告が出る。Python3.12ではあるモジュールが使えなくなるらしい。


◆◆Facade パターンとは◆◆

GoFによれば,Facade パターンの目的は, 「サブシステム内に存在する複数のインタフェースに1つの統一インタフェースを与える。Facade パターンはサブシステムの利用を容易にするための高レベルインタフェースを定義する。」

GoFによれば,次のような場合に Facade パターンを利用する。
・複雑なサブシステムに単純なインタフェースを提供したい場合。サブシステムは発展するにつれて,より複雑になっていく。たいていのパターンは,適用するとたくさんの小さなクラスが導入されることになる。それにより,サブシステムの再利用性が増し,カスタマイズも容易になる。しかしその一方で,サブシステムをカスタマイズする必要のないクライアントにとっては,そのサブシステムの利用が難しくなる。このような場合に,facade はサブシステムの単純なデフォルトのビューを提供してくれる。ほとんどのクライアントにとってはこのデフォルトのビューだけで十分である。サブシステムをカスタマイズする必要のあるクライアントだけが,facade を越えてサブシステムの内部まで見ることになる。
・ある抽象を実装しているクラスとクライアントの間に多くの依存関係がある場合。あるサブシステムをクライアントや他のサブシステムから切り離して,独立性や移植性を高めるために facade を導入する。
・サブシステムを階層化したい場合。各階層の各サブシステムへの入り口を定義するために facade を使う。複数のサブシステムが依存し合っている場合,それらのサブシステムが互いに facade を通してのみやりとりを行うようにすれば,それらの依存関係を単純にすることができる。

GoFによれば,Facade パターンの関連するパターンは次のようなものである。
Abstract Factory パターン:サブシステムとは独立した方法でサブシステム内のオブジェクトを生成するインタフェースを提供するために,Facade パターンと一緒に利用することができる。また,プラットフォームに特化したクラスを隠ぺいするという点で,Facade パターンに対する代替案として利用することもできる。
Mediator パターン:既存のクラスの機能を抽出しているという点で Facade パターンと似ている。しかし,Mediator パターンの目的は Colleague オブジェクト間のやりとりを抽出することであり,それらの機能を1か所に集中させて,Colleague オブジェクトには機能を持たせないようにする。Colleague オブジェクトは mediator の存在を知っており,Colleague オブジェクト同士で直接やりとりをする代わりに mediator とやりとりをする。一方 facade は,サブシステム内のオブジェクトの利用を容易にするために,そのインタフェースを抽出するだけである。facade は新たな機能を定義しないし,サブシステム内のクラスは facade の存在を知らない。
Singleton パターン:通常,facade には唯一性が要求される。したがって,facade にはしばしば Singleton パターンが適用される。

◆◆Facade パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
Database
(getProperties)

HtmlWriter
(title, pragraph, link, mailto, close)

PageMaker
(makeWelcomePage)
class Database は,ユーザリストからEメールアドレスとユーザ名を読み込みます。
class HtmlWriter は,Webページの html コードを部品として用意します。
class PageMaker は,facade オブジェクトであり,決まった様式のWebページをファイルとして出力します。

◆◆設定ファイル◆◆

ユーザリストを外部ファイルにします。Java サンプルではプロパティファイルを使いますが,Python サンプルでは Windows 標準の設定ファイル(ini ファイル)を使います。拡張子は必ずしも .ini でなくても良いそうです。サンプルでは .txt としました。

ここで使う設定ファイルは dict 辞書のようなもので,Eメールアドレスとユーザ名がキーとバリュー(値;Javaではプロパティ)となり対になっています。

区切り記号は普通は「:」ですがここでは Java サンプルと同じ「=」にしています。また,Java サンプルと違うところは「[]」で囲むセクションがあります。これは適当な名前にしてあります。あとは Java サンプルと同じです。

◆◆例外の取り扱い◆◆

外部ファイルのユーザリストの存在しないとき例外を出します。また,外部の html ファイルに出力できないときも例外をだします。

Java サンプルでは try-catch を使い,Python サンプルでは try-except を使います。外部ファイルに直接書き込むメソッドにおいて,Java サンプルでは例外を throw 投げていますが,Python ではあまり使わないので止めています。

例外の動作について完全には検証はできていません。ファイルの入出力の例外ですので,デバッグさえしてしまえばあとはまず出ることはないと考えます。

◆◆html コードの知識◆◆

html コードの知識は Facade パターンとはまったく関係ないのですがいちおう知っておいた方がよいと考えます。 html コードとブラウザでの見た目との関係をおよそ知ってもらえれば十分です。

サンプルが出力したhtml コードを巻末に掲載してあります。同時にそれをブラウザで見ているはずです。それを見ただけで html コードとブラウザでの見た目との関係の概略は判るでしょう。

html コードは「<>」の中に書きます。それをタグと言います。タグの外に書いたものはブラウザで見ることができます。タグは対になっているものが多いですからタグとタグの間に書いたものがブラウザで見ることができます。実際にそうなっていることを確認できるでしょう。見て判ると思いますが,部品化し易いコード体系になっています。

Web ページであることを示すために対タグ「html」「head」「body」をサンプルのように並べます。対タグ「head」の間には,html文書,タイトル,文字コードなどを宣言します。対タグ「body」の間には,ブラウザで見ることができる本文とそれを修飾するタグを書きます。

本文を修飾するタグについては詳細を省略します。サンプルで雰囲気だけつかんでください。なお,タグ「a」はリンクであり,ここにユーザリストの内容が使われています。

◆◆Facade パターンの実装◆◆

最初に注意しておきたいのは,48行目,62行目のメソッドの呼び出し時にインスタンス化してありません。Java サンプルでは private にしてインスタンス化を拒否しています。たしかにこのパターンはめずらしく抽象クラス(Java では interface)がないし,クラスの親子関係もないし,ましてやオーバーライドもないです。Python サンプルでは判りませんが,Java サンプルでは,インスタンスの型(所属クラス)と new する(インスタンス化する)クラスが違うことが普通です。クラスの親子関係とコンストラクタがなければインスタンス化しなくても支障がないのか!?

class Database では,クライアント;メインルーチンから与えられる Eメールアドレスよりユーザ名を検索するため設定ファイルを使っています。Python で設定ファイルを読み込むには ConfigParser または SafeConfigParser を使います。詳細な使い方はサンプルコードのとおりです。このサンプルでは設定ファイルを使っていますが,dict 辞書でも同じ効果が得られます。

class HtmlWriter では,html コードの部品を集めてあります。上でも書きましたが html コードは部品化しやすい構造になっています。このサンプルでは,くしくも使う順に並んでいますが,デタラメの順でかまいません。

使い方の典型例を示すのは class HtmlWriter クラスではなく,Facade オブジェクトである class PageMaker なのです。html コードの部品がなぜこの順に並んでいるのかはファイルの出力(巻末に掲示)を見れば判ります。Webページの作り方の詳細はここでは省略します。

Facade オブジェクトのおかげでクライアント;メインルーチンは Eメールアドレスとタイトルだけ指定するだけです。つくりたいWebページの様式に合わせてFacade オブジェクトである class PageMaker は作り替えが必要になるでしょう。

◆◆ソースコード◆◆

このWebページの左上隅からダウンロードできます。

1つのソースファイル,1つの設定ファイル,1つのサンプル出力があります。
・Facade.py;Facade パターンのPythonサンプル
・maildata.txt;設定ファイル形式のユーザリスト(Facade.pyと同じディレクトリ)
・welcome.html;サンプルプログラムの出力ファイル(Facade.pyと同じディレクトリ)

【Facade.py】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
from configparser import SafeConfigParser
from logging import getLogger
logger = getLogger(__name__)

class Database():
    def getProperties(self, dbname):
        filename = dbname + ".txt"
        config = SafeConfigParser()
        try:
            config.read(filename)
            prop = config["Properties"]
            return prop
        except IOError:
            logger.exception("Warning {} is not found.".format(filename))

class HtmlWriter():
    def __init__(self, writer):
        self.writer = writer
    def title(self, title):
        self.writer.write("<!DOCTYPE html> \n")
        self.writer.write("<html>\n")
        self.writer.write("<head>\n")
        self.writer.write(
            '<meta http-equiv="Content-Type" content="text/html; \
            charset=shift_jis">\n')
        self.writer.write("<title>{}</title>\n".format(title))
        self.writer.write("</head>\n")
        self.writer.write("<body>\n")
        self.writer.write("<h1>{}</h1>\n".format(title))
    def paragraph(self, msg):
        self.writer.write("<p>{}</p>\n".format(msg))
    def link(self, href, caption):
        self.paragraph('<a href="{}">{}</a>'.format(href, caption))
    def mailto(self, mailaddr, username):
        self.link("mailto:" + mailaddr, username)
    def close(self):
        self.writer.write("</body>\n")
        self.writer.write("</html>\n")
        self.writer.close()

class PageMaker():
    def makeWelcomePage(self, mailaddr, filename):
        try:
            mailprop = Database().getProperties("maildata")
            username = mailprop[mailaddr]
            writer = HtmlWriter(open(filename, mode="w"))
            writer.title("Welcome to {}'s page!".format(username))
            writer.paragraph("{}のページへようこそ。".format(username))
            writer.paragraph("メールまっていますね。")
            writer.mailto(mailaddr, username)
            writer.close()
            print("{} is created {} ({})".
                format(filename, mailaddr, username))
        except IOError as e:
            logger.exception(e)

def main():
    PageMaker().makeWelcomePage("hyuki@hyuki.com", "welcome.html")

if __name__ == '__main__':
    main()
####
#【maildata.txt】
[Properties]
hyuki@hyuki.com=Hiroshi Yuki
hanako@hyuki.com=Hanako Sato
tomura@hyuki.com=Tomura
mamoru@hyuki.com=Mamoru Takahashi

【welcome.html】(ファイルへの出力そのままです)
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
<title>Welcome to Hiroshi Yuki's page!</title>
</head>
<body>
<h1>Welcome to Hiroshi Yuki's page!</h1>
<p>Hiroshi Yukiのページへようこそ。</p>
<p>メールまっていますね。</p>
<p><a href="mailto:hyuki@hyuki.com">Hiroshi Yuki</a></p>
</body>
</html>

【上の html コードをブラウザで見たもの】
(上の html コードは次のWebページとして完成しているため,この記事全体のWebページとネスト(入れ子)になるのでブラウザによってはバグるかもしれない)
Welcome to Hiroshi Yuki's page!

Welcome to Hiroshi Yuki's page!

Hiroshi Yukiのページへようこそ。

メールまっていますね。

Hiroshi Yuki

inserted by FC2 system

以上

トップページに戻る
inserted by FC2 system