例題で学ぶデザインパターン #2.16:デザインパターン〈GoF〉Command
例題で学ぶ Jython/Swing デザインパターン《Jython2.5》
デザインパターン〈GoF〉Command
■ 概要
アプリケーションを作成する過程を通して、Jython/Swing によるデザインパターンを習得します。
この課題では、Swing/GUI を使って階層構造を持つ情報を提示します。〈GoF〉Composite/Iterator/Visitor/Command パターンを導入すると、if/for 文によるコードの汚染、配列の境界問題が解消されるので、要求仕様の変更にも柔軟に対処でき、簡潔で見通しの良いコードを記述できるようになります。
《Note》JPython1.1.x/Jython2.1.x 用に作成したセミナー課題を、Jython2.5 で再構成しました。
事例:モジュールを起動する
モジュールを起動すると、次のようなウィンドウが現れます。
$ jython2.5b3 -i step14/TreeEx.py JTree を頂点とするツリー/sports を頂点とする部分ツリー/football ノード
ボタン〔Text〕を押すと、選択したノードを頂点とする部分ツリーが、新たなウィンドウ JTextArea に表示されます。
事例:コードの解説
■ モジュール:TreeCommand.py
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 を付加できないからです。
Tips
》作業中です《