例題で学ぶデザインパターン #2.16:デザインパターン〈GoF〉Command

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

例題で学ぶ Jython/Swing デザインパターン《Jython2.5》
デザインパターンGoF〉Command

《著》越智ことり+小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪2003/05/23 ● 第2版♪2009/04/03

■ 概要

アプリケーションを作成する過程を通して、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

》作業中です《

Last updated♪09/06/16