Abstract Factoryパターン

結城浩さんの「Java言語で学ぶデザインパターン入門」を題材にpythonデザインパターンを書いてみる。
今回の題材はAbstract Factoryパターン。名前の通り抽象的な工場で、抽象的な部品を組み合わせて抽象的な製品を作るらしい。
わかりにくいが、要は部品の具体的な実装は気にせずに部品を組み立てて製品としてまとめてしまうというものみたい。
具体的な実装はサブクラスで行う。
サンプルのプログラムは階層構造を持ったリンク集をHTMLファイルとして作るもの。
はじめに抽象的な工場、部品、製品を含む部分(factory.py)。

# -*- coding: utf-8 -*-
class Item(object):
    def __init__(self, caption):
        self.caption = caption
    def makeHTML(self):
        pass

class Link(Item):
    def __init__(self, caption, url):
        super(Link, self).__init__(caption)
        self.url = url

class Tray(Item):
    def __init__(self, caption):
        super(Tray, self).__init__(caption)
        self.tray = []
    def add(self, item):
        self.tray.append(item)

class Page(object):
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.content = []
    def add(self, item):
        self.content.append(item)
    def output(self):
        self.filename = "%s.html" %self.title
        writer = open(self.filename, "w")
        writer.write(self.makeHTML())
        writer.close()
        print "%sを作成しました。" %self.filename
    def makeHTML(self):
        pass

class Factory(object):
    @classmethod
    def getFactory(cls, classname):
        module, kls = classname.rsplit(".", 1)
        return getattr(__import__(module), kls)()
    def createLink(self, caption, url):
        pass
    def createTray(self, caption):
        pass
    def createPage(self, title, author):
        pass

ItemクラスはLinkクラスとTrayクラスを統一的に扱うために両者のスーパークラスになる。
LinkクラスはHTMLのリンクを抽象的に表現したクラス。Trayクラスはaddメソッドを使って複数の項目をひとまとめにする。
PageクラスはHTMLページ全体を抽象的に表現したクラス。addメソッドで項目を追加、outoutメソッドで自分自身の内容を
ファイルに書き込む。
Factoryクラスは抽象的な工場。getFactoryメソッドはクラス名を指定して工場のインスタンスをつくる。
getattr()関数は指定したオブジェクトの属性を返してくる。ここではclassnameで指定されたクラスのモジュールをインポート
して、モジュール内のクラスのインスタンスが返り、そのインスタンスを新しくつくってgetFactoryメソッドの戻り値にしている。
次に動作テスト用の実行部分(abstract_factory_main.py)。

# -*- coding: utf-8 -*-
from factory import *
import sys
factory = Factory.getFactory(sys.argv[1])

asahi = factory.createLink("朝日新聞", "http://www.asahi.com/")
yomiuri = factory.createLink("読売新聞", "http://www.yomiuri.co.jp/")
us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/")
jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/")
excite = factory.createLink("Exite", "http://www.excite.com/")
google = factory.createLink("Google", "http://www.google.com/")

traynews = factory.createTray("新聞")
traynews.add(asahi)
traynews.add(yomiuri)

trayyahoo = factory.createTray("Yahoo!")
trayyahoo.add(us_yahoo)
trayyahoo.add(jp_yahoo)

traysearch = factory.createTray("サーチエンジン")
traysearch.add(trayyahoo)
traysearch.add(excite)
traysearch.add(google)

page = factory.createPage("LinkPage", "name")
page.add(traynews)
page.add(traysearch)
page.output()

抽象的な工場で抽象的な部品をつくり、抽象的な製品を組み立てる。具体的な工場のクラス名はコマンドラインの引数で
指定する。この引数からgetFactoryで工場をつくって変数factoryに代入する。後はLink,Trayをつくってひとまとめにして
から、Pageをつくってoutput。
ここからは具体的な工場、部品、製品を含む部分(listfactory.py)。

import factory
class ListFactory(factory.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)

class ListLink(factory.Link):
    def __init__(self, caption, url):
        super(ListLink, self).__init__(caption, url)
    def makeHTML(self):
        return ' <li><a href="%s">%s</a></li>\n' %(self.url, self.caption)

class ListTray(factory.Tray):
    def __init__(self, caption):
        super(ListTray, self).__init__(caption)
    def makeHTML(self):
        self.buffer = []
        self.buffer.append("<li>\n")
        self.buffer.append("%s\n" %self.caption)
        self.buffer.append("<ul>\n")
        for item in self.tray:
            self.buffer.append(item.makeHTML())
        self.buffer.append("</ul>\n")
        self.buffer.append("</li>\n")
        return "\n".join(self.buffer)

class ListPage(factory.Page):
    def __init__(self, title, author):
        super(ListPage, self).__init__(title, author)
    def makeHTML(self):
        self.buffer = []
        self.buffer.append("<html><head><title>%s</title></head>\n" %self.title)
        self.buffer.append("<body>\n")
        self.buffer.append("<h1>%s</h1>\n" %self.title)
        self.buffer.append("<ul>\n")
        for item in self.content:
            self.buffer.append(item.makeHTML())
        self.buffer.append("</ul>\n")
        self.buffer.append("<hr><address>%s</address>" %self.author)
        self.buffer.append("</body></html>\n")
        return "\n".join(self.buffer)

ListFactoryクラスはFactoryクラスのcreateLink,createTray,createPageを実装。単にListLink,ListTray,ListPageのインスタンス
を返すだけ。
ListLinkクラスはmakeHTMLメソッドを実装してHTMLの断片をつくる。ListTrayクラスもmakeHTMLメソッドを実装。HTMLの断片をリストに
集めて最後につなげる。ListPageクラスでもmakeHTMLメソッドを実装してページの構成をつくる。
最後に別の具体的な工場を含む部分(tablefactory.py)。

import factory
class TableFactory(factory.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)

class TableLink(factory.Link):
    def __init__(self, caption, url):
        super(TableLink, self).__init__(caption, url)
    def makeHTML(self):
        return '<td><a href="%s">%s</a></td>\n' %(self.url, self.caption)

class TableTray(factory.Tray):
    def __init__(self, caption):
        super(TableTray, self).__init__(caption)
    def makeHTML(self):
        self.buffer = []
        self.buffer.append("<td>")
        self.buffer.append('<table width="100%" border="1"><tr>')
        self.buffer.append('<td bgcolor="#cccccc" align="center" colspan="%s"><b>%s</b></td>' %(len(self.tray), self.caption))
        self.buffer.append("</tr>\n")
        self.buffer.append("<tr>\n")
        for item in self.tray:
            self.buffer.append(item.makeHTML())
        self.buffer.append("</tr></table>")
        self.buffer.append("</td>")
        return "\n".join(self.buffer)

class TablePage(factory.Page):
    def __init__(self, title, author):
        super(TablePage, self).__init__(title, author)
    def makeHTML(self):
        self.buffer = []
        self.buffer.append("<html><head><title>%s</title></head>\n" %self.title)
        self.buffer.append("<body>\n")
        self.buffer.append("<h1>%s</h1>\n" %self.title)
        self.buffer.append('<table width="80%" boder="3">\n')
        for item in self.content:
            self.buffer.append("<tr>%s</tr>" %item.makeHTML())
        self.buffer.append("</table>\n")
        self.buffer.append("<hr><address>%s</address>" %self.author)
        self.buffer.append("</body></html>\n")
        return "\n".join(self.buffer)

実際に実行するときは、

C:\works\python\book1>python abstract_factory_main.py listfactory.ListFactory
LinkPage.htmlを作成しました。

とすると、箇条書きを元にしたデザインのHTMLファイルがつくられる。

C:\works\python\book1>python abstract_factory_main.py tablefactory.TableFactory
LinkPage.htmlを作成しました。

とすると、表組みを元にしたデザインになる。