《余録》Swing/Jython - GoF を反面教師に, パターンの効能

前の記事記事一覧次の記事

Java プログラマーのための Python 導入ガイド〈初級/応用編〉《Jython2.5》
《余録》GoF を反面教師に, パターンの効能

《著》小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪2003/05/23 ● 第2版♪2009/04/03
》作業中です《

■ 概要

フォルダー/ファイルの階層構造を Swing/GUI を利用して「簡単に」閲覧できるツールがあると便利です。

 セミナー課題では、JTree/DefaultMutableTreeNode と同等の機能を「実現する」方法を紹介しました。
 ただし、GoF を鵜呑みにすると Python の特徴を活かせず、損をする場面もあるので、注意が必要です。
 ここでは「GoF で積み残された問題を解消する」方法を紹介するとともに、その問題点について考察します。

 《Note》JPython1.1.x/Jython2.1.x 用に作成したセミナー課題を、Jython2.5 で再構成しました。

パターンを導入する:劇的 before/after !?

今回のセミナー課題では〈GoF〉Composite/Iterator/Visitor/Command パターンを導入しました。これらを導入する前後のコードを比較することで、パターンの効能(簡潔で見通しが良く、要求仕様の変更にも柔軟に対処できる)について考察します。

■ 〈GoF〉Composite

if による「条件」分岐の呪縛を解くには、要素分解を施して抽象化(単一/複合オブジェクトの違いを捨象)したコードを記述する術が必要です。

## ---------------------------------------- before

各ノードをたどるたびに、型(単一オブジェクト/複合オブジェクト)の違いを識別する必要があるとともに、リファクタリングにより重複するコードを抽出するのも困難です。

## ---------------------------------------- after
class FileNode(Node):
    def getsize(self):
        return getsize(self.path)
class DirNode(Node):
    def getsize(self):
        return reduce(lambda s,e: s+e.getsize(), self, 0)

オブジェクト自身の内部構造だけを知る必要があるだけ(分割統治の原則)なので、相互に依存しない独立したコードを記述できます。コードを記述した段階では未解決の問題は、メッセージ getsize を転送することで「他のオブジェクト」に委ねます。

⇒ 続きはこちら 〈GoF〉Composite を導入する

■ 〈GoFIterator

for による「条件」反復の呪縛を解くには、より抽象的な(内部構造に依存しない)制御構造を記述する術が必要です。

## ---------------------------------------- before
class DirNode(Node):
    def preorder(self):
        return reduce(
            lambda s,e: s+e.userObject.preorder(),
            self.treeNode.children(),             # (x_x) too bad
            [self])

条件を満たす間(子ノード children が存在する)は、次の要素(対応するモデル userObject の存在)を参照しながら、本質的な操作(各ノードを前順走査 preorder でたどる)を繰り返します。しかし、これらのコードは、オブジェクトの内部構造(userObject, treeNode.children)を知らないと記述できません。

## ---------------------------------------- after
class DirNode(Node):
    def preorder(self):
        return reduce(
            lambda s,e: s+e.preorder(), 
            self, 
            [self])

オブジェクトの内部構造を知る必要がない(情報隠蔽の原則)ので、どのように実現するか(how)に依存しない、何を実現したいか(what)に着目したコードを記述できます。

⇒ 続きはこちら 〈GoF〉Iterator が必要に

■ 〈GoF〉Visitor

if による「条件」分岐の呪縛を解くには、要素分解を施して(相互に依存しない)独立したコードを記述する術が必要です。

## ---------------------------------------- before
class Model(object):
    def createNodes(self, path, parent):
        for e in listdir(path):
            current = join(path, e)
            if isdir(current):
                child = DefaultMutableTreeNode(DirNode(current))
                child.userObject.treeNode = child     # (x_x) too bad
                self.createNodes(current, child)
            else:
                child = DefaultMutableTreeNode(FileNode(current))
                child.userObject.treeNode = child     # (x_x) too bad
            parent.add(child)

各ノードをたどるたびに、型(フォルダー/ファイル)の違いを識別する必要があるとともに、リファクタリングにより重複するコードを抽出するのも困難です。

## ---------------------------------------- after
class TreeVisitor(Visitor):
    def visitFileNode(self, node, parent):
        pass
    def visitDirNode(self, node, parent):
        for e in node:
            child = DefaultMutableTreeNode(e)
            parent.add(child)
            e.accept(TreeVisitor(), child)

オブジェクト自身の内部構造だけを知る必要があるだけ(分割統治の原則)なので、相互に依存しない独立したコードを記述できます。コードを記述した段階では未解決の問題は、メッセージ accept を受け取った瞬間に「オブジェクト自身」が解決します。

⇒ 続きはこちら 〈GoF〉Visitor を導入する

■ 〈GoF〉Command

if による「条件」分岐の呪縛を解くには、要素分解を施して(外部情報に頼らず)オブジェクト自身の識別性を利用したコードを記述する術が必要です。

## ---------------------------------------- before
class View(JPanel):
    def actionPerformed(self, e):
        if e.source.actionCommand == "Text":     # (x_x) too bad
            self.text_actionPerformed()
        if e.source.actionCommand == "Tree":     # (x_x) too bad
            self.tree_actionPerformed()

オブジェクトを識別するための情報 actionCommand を外部から与えるとともに、すべての場合(条件式)を網羅する必要があります。しかし、これらの付加情報("Text", "Tree")は冗長なので、オブジェクトの識別性を利用すると不要になります。

## ---------------------------------------- after
class View(JPanel):
    def actionPerformed(self, e):
        e.source.command()

オブジェクトの識別性を利用できるので、すべての可能性を(静的に)確定する必要がなく、何をすべきかは実行時に(動的に)解決します。

⇒ 続きはこちら 〈GoF〉Command が必要に

Tips

》作業中です《

Last updated♪09/05/26