2010年11月24日水曜日

第13章 Visitorパターン : 構造を渡り歩きながら仕事をする ~design patterns with python~



気がつけば、もう本の半分以上の量を消化していました。
今回はVisitorパターン。キーワードは『データ構造と処理を分離する』です。

例えば、データモデルクラスを作って、その中にデータ処理のメソッドなんかを追加すると、モデルクラスが肥大してしまうことがままありました。
(アクセサぐらいならまだいいのですが、シリアライザやパーサなんかもついつい入れがちでした)
そんな時は大抵別のクラスを用意してメソッド類を引越ししてやるのですが、色んなモデルクラスの色んな処理クラスが混在して見通しが悪くなりがちでした。
Visitorパターンは、こうしたケースに一貫した解決を与えるいい方法ではないかと思いました。

また書籍内の例を見ても分かりますが、CompositeパターンやDecoratorパターンなどの再帰的なパターンと相性が良さそうです。

--

visit()のオーバーロードは特に意味が無いようなので、メソッドを分けてあります。
iterator()は単にリストを返すようにしてあります。
練習問題13-1には、対象のファイルが存在するディレクトリのパスも併せて出力するように勝手に変更 してあります。

実行結果
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31850)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1850)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (650)
/root/usr/hanako/memo.tex (300)
/root/usr/hanako/index.html (350)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)

HTML files are:
diary.html (100) in /root/usr/yuki
index.html (350) in /root/usr/hanako

ソースコード
#!/usr/bin/env python
# -*- coding: utf8 -*-

class FileTreatmentException(Exception):
    def __init__(self):
        pass

class __Element:
    def __init__(self): raise NotImplementedError
    def accept(self, visitor): raise NotImplementedError

class _Entry(__Element):
    def __init__(self): raise Exception("Abstract class")
    def getName(self): raise NotImplementedError
    def getSize(self): raise NotImplementedError
    def add(self, entry): raise FileTreatmentException
    def iterator(self): raise FileTreatmentException
    def __str__(self): return "%s (%d)" % (self.getName(), self.getSize())


class File(_Entry):
    def __init__(self, name, size):
        self.__name = name
        self.__size = size

    def getName(self): return self.__name
    def getSize(self): return self.__size

    def accept(self, visitor):
        visitor.visitFile(self)


class Directory(_Entry):
    def __init__(self, name):
        self.__name = name
        self.directory = []

    def getName(self): return self.__name
    def getSize(self): return sum([e.getSize() for e in self.directory])

    def add(self, entry):
        self.directory.append(entry)

    def iterator(self):
        return self.directory

    def accept(self, visitor):
        visitor.visitDirectory(self)


class _Visitor:
    def __init__(self): raise Exception("Abstract class")
    def visitFile(self, thefile): raise NotImplementedError
    def visitDirectory(self, directory): raise NotImplementedError

class ListVisitor(_Visitor):
    def __init__(self):
        self.__currentdir = ""

    def visitFile(self, thefile):
        print self.__currentdir+"/"+str(thefile)

    def visitDirectory(self, directory):
        print self.__currentdir+"/"+str(directory)

        # ディレクトリ内を走査するために一旦入る
        savedir = self.__currentdir
        self.__currentdir = self.__currentdir+"/"+directory.getName()

        for e in directory.iterator():
            e.accept(self)

        # 走査が終わったらディレクトリを出る
        self.__currentdir = savedir

#13-1
class FileFindVisitor(_Visitor):
    def __init__(self, pattern):
        self.__pattern = pattern
        self.__currentdir = ""
        self.__founds = [] #(file, dirname which contains the file)

    def visitFile(self, thefile):
        if thefile.getName().find(self.__pattern) is not -1:
            self.__founds.append((thefile, self.__currentdir))

    def visitDirectory(self, directory):
        savedir = self.__currentdir
        self.__currentdir = self.__currentdir+"/"+directory.getName()

        for e in directory.iterator():
            e.accept(self)

        self.__currentdir = savedir

    def getFoundFiles(self):
        return self.__founds


def main():
    try:
        print "Making root entries..."
        m_rootdir = Directory("root")
        m_bindir = Directory("bin")
        m_tmpdir = Directory("tmp")
        m_usrdir = Directory("usr")
        m_rootdir.add(m_bindir)
        m_rootdir.add(m_tmpdir)
        m_rootdir.add(m_usrdir)
        m_bindir.add(File("vi", 10000))
        m_bindir.add(File("latex", 20000))
        m_rootdir.accept(ListVisitor())

        print ""
        print "Making user entries..."
        m_yuki = Directory("yuki")
        m_hanako = Directory("hanako")
        m_tomura = Directory("tomura")
        m_usrdir.add(m_yuki)
        m_usrdir.add(m_hanako)
        m_usrdir.add(m_tomura)
        m_yuki.add(File("diary.html", 100))
        m_yuki.add(File("Composite.java", 200))
        m_hanako.add(File("memo.tex", 300))
        m_hanako.add(File("index.html", 350))
        m_tomura.add(File("game.doc", 400))
        m_tomura.add(File("junk.mail", 500))
        m_rootdir.accept(ListVisitor())

        #13-1
        print ""
        print "HTML files are:"
        ffv = FileFindVisitor(".html")
        m_rootdir.accept(ffv)
        for (f,d) in ffv.getFoundFiles():
            print "%s in %s" % (str(f), d)

    except FileTreatmentException:
        print "FileTreatmentException raised"

if __name__=='__main__': main()

0 件のコメント:

コメントを投稿