GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Abstract Factory(アブストラクトファクトリ)
Abstract Factory パターン(アブストラクト・ファクトリ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 関連するインスタンス群を生成するための API を集約することによって、複数のモジュール群の再利用を効率化することを目的とする。日本語では「抽象的な工場」と翻訳される事が多い。Kit パターンとも呼ばれる
UML class and sequence diagram
UML class diagram
■ "Abstract Factory"のサンプルプログラム
Abstract Factoryパターンとは、抽象的な工場から、抽象的な部品を組み合わせて抽象的な製品を作るらしいです。
実際に、Abstract FactoryのPython実装コードを動かしてみて、抽象的な工場をイメージを理解したいと思います。ここで取り上げるサンプルプログラムは、階層構造を持ったリンク集をHTMLファイルとして作るものです。
-
ListFactory
モードで、動作させると、リスト形式のリンク集のHTMLファイルが生成されます。 -
TableFactory
モードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。
(1) ListFactoryを動かしてみる
まずは、LinkベースのWebページを作成するコードを動かしてみます。
$ python Main.py ListFactory
[LinkPage.html] was created.
LinkPage.html
というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
(2) TableFactoryを動かしてみる
つづいて、TableベースのWebページを作成するコードを動かしてみます。
$ python Main.py TableFactory
[LinkPage.html] was created.
LinkPage.html
というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/AbstractFactory
- ディレクトリ構成
.
├── Main.py
└── factory
├── __init__.py
├── factory.py
├── listfactory
│ ├── __init__.py
│ └── list_factory.py
└── tablefactory
├── __init__.py
└── table_factory.py
(1) AbstractProduct(抽象的な製品)の役
AbstractProduct
役は、AbstractFactory
役によって、作り出される抽象的な部品や製品のインタフェースを定めます。サンプルプログラムでは、Link
クラス、Tray
クラス、Page
クラスが、この役を努めます。
import sys
from abc import ABCMeta, abstractmethod
... (snip)
class Item(metaclass=ABCMeta):
def __init__(self, caption):
self.caption = caption
@abstractmethod
def makeHtml(self):
pass
class Link(Item, metaclass=ABCMeta):
def __init__(self, caption, url):
super().__init__(caption)
self.url = url
class Tray(Item, metaclass=ABCMeta):
def __init__(self, caption):
super().__init__(caption)
self.tray = []
def add(self, item):
self.tray.append(item)
class Page(metaclass=ABCMeta):
def __init__(self, title, author):
self.title = title
self.author = author
self.content = []
def add(self, item):
self.content.append(item)
def output(self):
try:
filename = self.title + '.html'
writer = open(filename, 'w')
writer.write(self.makeHtml())
writer.close()
print("[" + filename + "]" + " was created.")
except Exception as e:
print(e)
sys.exit(1)
@abstractmethod
def makeHtml(self):
pass
(2) AbstractFactory(抽象的な工場)の役
AbstractFactory
役は、AbstractProduct
役のインスタンスを作り出すためのインタフェースを定めます。
サンプルプログラムでは、Factory
クラスがこの役を勤めます。
import sys
from abc import ABCMeta, abstractmethod
class Factory(metaclass=ABCMeta):
@abstractmethod
def createLink(self, caption, url):
pass
@abstractmethod
def createTray(self, caption):
pass
@abstractmethod
def createPage(self, title, author):
pass
... (snip)
(3) Client(依頼者)の役
Client
役は、AbstractFactory
役とAbstractProduct
役のインタフェースだけを使って仕事を行います。
Client
役は、具体的な部品や製品や工場については、知りません。なお、サンプルプログラムでは、startMain
メソッドが、この役を努めます。
import sys
import inspect
import factory
def startMain(factoryObject):
asahi = factoryObject.createLink("Asahi", "http://www.asahi.com")
yomiuri = factoryObject.createLink("Yomiuri", "http://www.yomiuri.co.jp")
us_yahoo = factoryObject.createLink("Yahoo", "http://www.yahoo.com")
jp_yahoo = factoryObject.createLink("Yahoo!Japan", "http://www.yahoo.co.jp")
google = factoryObject.createLink("Google", "http://www.google.com")
excite = factoryObject.createLink("Excite", "http://www.excite.co.jp")
traynews = factoryObject.createTray("Newspaper")
traynews.add(asahi)
traynews.add(yomiuri)
trayyahoo = factoryObject.createTray("Yahoo!")
trayyahoo.add(us_yahoo)
trayyahoo.add(jp_yahoo)
traysearch = factoryObject.createTray("Search Engine")
traysearch.add(trayyahoo)
traysearch.add(excite)
traysearch.add(google)
page = factoryObject.createPage("LinkPage", "Hiroshi Yuki")
page.add(traynews)
page.add(traysearch)
page.output()
if __name__ == '__main__':
for _, plugin in inspect.getmembers(factory, inspect.isclass):
if plugin.__name__ == sys.argv[1]:
startMain(plugin())
(4) ConcreteProduct(具体的な製品)の役
ConcreteProduct
役は、AbstractProduct
役のインタフェースを実装します。サンプルプログラムでは、以下のクラスが、この役を努めます。
-
ListLink
クラス、ListTray
クラス、ListPage
クラス -
TableLink
クラス、TableTray
クラス、TablePage
クラス
from factory.factory import Factory, Link, Tray, Page
... (snip)
class ListLink(Link):
def __init__(self, caption, url):
super().__init__(caption, url)
def makeHtml(self):
return ' <li><a href="{}">{}</a></li>\n'.format(self.url, self.caption)
class ListTray(Tray):
def __init__(self, caption):
super().__init__(caption)
def makeHtml(self):
buf = []
buf.append('<li>\n')
buf.append(self.caption + '\n')
buf.append('<ul>\n')
for item in self.tray:
buf.append(item.makeHtml())
buf.append('</ul>\n')
buf.append('</li>\n')
return ''.join(buf)
class ListPage(Page):
def __init__(self, title, author):
super().__init__(title, author)
def makeHtml(self):
buf = []
buf.append('''
<html>
<head><title>{}</title></head>
'''.format(self.title))
buf.append('<body>\n')
buf.append('<h1>{}</h1>'.format(self.title))
buf.append('<ul>')
for item in self.content:
buf.append(item.makeHtml())
buf.append('</ul>')
buf.append('<hr><adress>{}</adress>'.format(self.author))
buf.append('</body>\n</html>\n')
return ''.join(buf)
from factory.factory import Factory, Link, Tray, Page
... (snip)
class TableLink(Link):
def __init__(self, caption, url):
super().__init__(caption, url)
def makeHtml(self):
return '<td><a href={}>{}</a></td>'.format(self.url, self.caption)
class TableTray(Tray):
def __init__(self, caption):
super().__init__(caption)
def makeHtml(self):
buf = []
buf.append('<td>')
buf.append('<table width="100%" border="1"><tr>')
buf.append('<td bgcolor="#cccccc" algin="center" colsapn="{}"><b>{}</b></td>'.format(len(self.tray), self.caption))
buf.append('</tr>\n')
buf.append('<tr>\n')
for item in self.tray:
buf.append(item.makeHtml())
buf.append('</tr></table>')
buf.append('</td>')
return ''.join(buf)
class TablePage(Page):
def __init__(self, title, author):
super().__init__(title, author)
def makeHtml(self):
buf = []
buf.append('''
<html>
<head><title>{}</title></head>
'''.format(self.title))
buf.append('<body>\n')
buf.append('<h1>{}</h1>'.format(self.title))
buf.append('<table width="80%" border="3">\n')
for item in self.content:
buf.append('<tr>{}</tr>'.format(item.makeHtml()))
buf.append('</table>')
buf.append('<hr><adress>{}</adress>'.format(self.author))
buf.append('</body>\n</html>\n')
return ''.join(buf)
(5) ConcreteFactory(具体的な工場)の役
ConcreteFactory
役は、AbstractFactory
役のインタフェースを実装します。サンプルプログラムでは、以下のクラスが、この役を努めます。
-
ListFactory
クラス -
TableFactory
クラス
from factory.listfactory.list_factory import ListFactory
from factory.tablefactory.table_factory import TableFactory
__all__ = [
"ListFactory",
"TableFactory"
]
from factory.factory import Factory, Link, Tray, Page
class ListFactory(Factory):
def createLink(self, caption, url):
return ListLink(caption, url)
def createTray(self, caption):
return ListTray(caption)
def createPage(self, title, author):
return ListPage(title, author)
... (snip)
from factory.factory import Factory, Link, Tray, Page
class TableFactory(Factory):
def createLink(self, caption, url):
return TableLink(caption, url)
def createTray(self, caption):
return TableTray(caption)
def createPage(self, title, author):
return TablePage(title, author)
... (snip)