例題で学ぶデザインパターン #18:デザインパターン〈GoF〉Visitor, #2
例題で学ぶ Jython/Swing デザインパターン《Jython2.5》
デザインパターン〈GoF〉Visitor
■ 概要
例題により、アプリケーションを作成する過程を通して、Jython/Swing によるデザインパターンを習得します。
この課題では、Swing/GUI を使って階層構造を持つ情報を提示します。〈GoF〉Composite/Iterator/Visitor/Command パターンを導入すると、if/for 文によるコードの汚染、配列の境界問題が解消されるので、要求仕様の変更にも柔軟に対処でき、簡潔で見通しの良いコードを記述できるようになります。
《Note》JPython1.1.x/Jython2.1.x 用に作成したセミナー課題を、Jython2.5 で再構成しました。
事例:コードの解説
■ モジュール:TreeCommand.py
... from TreeVisitor import TextVisitor, TreeVisitor class TreeCommand(Command): def __call__(self): path = self.view.lastSelectedPathComponent model = path.userObject visitor = TreeVisitor(model) model.accept(visitor, visitor.root) view = JTree( visitor.root, valueChanged = self.view.valueChanged, ) ... class TextCommand(Command): def __call__(self): path = self.view.lastSelectedPathComponent model = path.userObject visitor = TextVisitor() model.accept(visitor, visitor.root) view = JTextArea( font = Font("Courier", Font.PLAIN, 12), text = str(visitor.root), ) ... ## ---------------------------------------- class PopWindow(JFrame): def __init__(self, **keys): super(self.__class__,self).__init__( defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE, **keys) def open(self, x=900, y=200, w=200, h=150): self.bounds = x, y, w, h self.visible = True ## ========================================
■ モジュール:TreeComposite.py
class TreeNode(object): def accept(self, visitor, parent): raise NotImplementedError(self.__class__.__name__) class Leaf(TreeNode): def accept(self, visitor, parent): visitor.visitLeaf(self, parent) class Node(TreeNode): def accept(self, visitor, parent): visitor.visitNode(self, parent)
■ モジュール:TreeView.py
... from TreeVisitor import TextVisitor class View(JPanel): def valueChanged(self, e): model = e.path.lastPathComponent.userObject visitor = TextVisitor() model.accept(visitor, visitor.root) self.textArea.text = str(visitor.root)
Tips
》作業中です《
@
from java.awt import Font from javax.swing import JFrame from javax.swing import JScrollPane from javax.swing import JTextArea from javax.swing import JTree from TreeModel import Model class Command(object): # Command::Command def __init__(self, view): self.view = view
インスタンス属性 self.view を介して、ツリーの各ノード DefaultMutableTreeNode を参照できます。
def __call__(self): raise NotImplementedError, self.__class__.__name__
特殊メソッド __call__ は、演算子 () に呼応して呼び出されます。実行時に例外 NotImplementedError を生成して、子孫クラスでこのメソッドを定義するように、プログラマーに注意を促します。
def open(self, path, view): window = PopWindow( title = str(path), ) window.contentPane.add(view) window.open() class TreeCommand(Command): # Command::ConcreteCommand def __call__(self): path = self.view.selectionPath.lastPathComponent model = Model(path) view = JTree( model.root.treeNode, valueChanged = self.view.valueChanged, ) view = JScrollPane(view) self.open(path, view) class TextCommand(Command): # Command::ConcreteCommand def __call__(self): path = self.view.selectionPath.lastPathComponent view = JTextArea( font = Font("Courier", Font.PLAIN, 12), text = str(path.userObject), ) view = JScrollPane(view) self.open(path, view)
子孫クラス TreeCommand/TextCommand では、抽象メソッド __call__ に対する具体的な動作を規定します。
■ モジュール:TreeView.py
... from TreeCommand import TreeCommand, TextCommand class View(JPanel): def init_buttons(self): panel = JPanel(GridLayout(1, 0)) for label, command in [ ("Tree", TreeCommand), ("Text", TextCommand), ]: button = CommandButton( label = label, command = command(self.tree), actionPerformed = self.actionPerformed, ) panel.add(button) return panel def actionPerformed(self, e): e.source.command()
前述した actionPerformed と違って、if 文が不要になるので、コードが汚染されるのを防ぎます。また、インスタンス属性 .command によって参照されるのは「呼び出し可能オブジェクト」なので、演算子 () に呼応して適切なメソッドを「動的に」起動します。
《Note》冗長なメソッド呼び出しが不要になるので、簡潔で見通しの良いコードを記述できるだけでなく、特定のプロトコルによってコードが汚染されるのを防ぎます。すると、開放閉鎖原則に沿って、このモジュールを閉じることができます。
class CommandButton(JButton): pass
クラス CommandButton は、便宜的に設けたもので、その本体は空 pass です。というのは、Java で記述されたリソースに対しては、インスタンス属性 .command を付加できないからです。