《余録》Swing/Jython - GoF を反面教師に, パターンの効能
Java プログラマーのための Python 導入ガイド〈初級/応用編〉《Jython2.5》
《余録》GoF を反面教師に, パターンの効能
■ 概要
フォルダー/ファイルの階層構造を 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 を導入する《 で
■ 〈GoF〉Iterator
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
》作業中です《