ビューの構成
見た目がすべてではありませんが、やはり第一印象は大切にしたいものです。
理想的な上司
親クラスには、理想的な上司のような存在であって欲しいものです。
class DefaultFrame(JFrame): defaultTitle = "Default Title" defaultSize = Dimension(200, 100) def __init__(self, title=None, size=None): self.initialize() self.initializeFrame(title, size) self.initializeComponent() self.visible = True def initialize(self): pass def initializeFrame(self, title=defaultTitle, size=defaultSize): JFrame.__init__(self, defaultCloseOperation=JFrame.EXIT_ON_CLOSE, title=title, size=size) def initializeComponent(self): pass
クラスDefaultFrameは、抽象クラス(理想)として、子孫クラスに共通する特性を規定します。メソッド__init__は、そのテンプレートとして、共通するフレームワークだけを規定します。ここで、initialize/initializeComponentは、抽象メソッドとして、子孫クラスで『必要なら』再定義することを想定しています。メソッドinitializeFrameは、ウィンドウのタイトルに指定する文字列titleと、大きさ(幅/高さ)sizeを設定します。ただし、実際の処理は、親クラスJFrameで規定したJFrame.__init__に委ねます。
ここで着目して欲しいのは、親/子孫クラスの役割分担です。DefaultFrameでは、役割分担を規定するだけで、作業には介入しません。必要な要員/予算だけ確保して、現場には口を出さない、理想的な上司を演じます。ただし、名前Default..から受ける印象とは違って、暗黙の規定値(デフォルト)にはならないので、子孫クラスでは「親の七光に肖りたい」という意思を表明する必要があります。すると、何か問題があれば親が事態を収拾して、何か功績があれば子孫の栄誉となります。
理想的な部下
子孫クラスには、理想的な部下のような存在であって欲しいものです。
class Puzzle15Frame(DefaultFrame): def __init__(self, title=None, size=None): DefaultFrame.__init__(self, title, size) def initialize(self): self.panel = PuzzlePanel() def initializeComponent(self): self.layout = BorderLayout() self.add(self.panel)
クラスPuzzle15Frameは、具象クラス(現実)として、親クラスDefaultFrameで規定した特性を再定義したり、実質的な作業を他に委ねたりします。
メソッド__init__では、実引数title/sizeを、DefaultFrame.__init__に委ねるだけで、自分は何もしません。これは、既存のフレームワークに従うことを宣言したことになります。つまり「自分は何もしない」ことに意義があるのです。
メソッドinitializeでは、そのクラスに固有の特性を規定します。インスタンス属性panelは、フレーム内に配置するパネルPuzzlePanelを保持します。
メソッドinitializeComponentでは、ビュー(視覚部品)の特性を規定します。指定したレイアウトBorderLayoutに従って、パネルを追加addします。
ここで着目して欲しいのは、親クラスの規定に従うなら何をしても自由ですが、それに従わないならすべて自己責任となることです。Puzzle15Frameの責務には、上司の指示を仰ぐ行為も含まれます。このパズルの成否はすべて、部下Puzzle15Frameの自主的な活動次第となります。
理想的な作業机
同じ作業机を与えても、そこには個性が反映されます。机の上を整理整頓しておくと作業がはかどるように、部品の構成管理にも配慮したいものです。
class PuzzlePanel(JPanel): dim = 4 dim2 = dim*dim def __init__(self): self.initialize() self.initializeComponent() self.shuffle(1000) def initializeComponent(self): self.mouseClicked = self.this_mouseClicked
クラスPuzzlePanelは、ビューを構成する部品の一つとして、アプリケーションに固有の機能を提供します。
インスタンス属性dim/dim2は、すべてのインスタンスに共通する情報(定数項)を規定します。ただし、要求仕様に変更があると、この箇所が問題となるでしょう。
メソッド__init__では、親クラスJPanelで規定したプロトコルに従って、パネルを再構成します。initializeは、そのクラスに固有の特性を規定するもので、先に示した方法でパネル内にコマを配置します。shuffleは、指定した1,000個の乱数を発生させて、それをもとに任意のコマを移動させます。
メソッドinitializeComponentでは、ビュー(視覚部品)の特性を規定します。インスタンス属性mouseClickedは、イベントハンドラとして、関数オブジェクトthis_mouseClickedを保持します。
理想的な文房具
同じ文房具を与えられても、作業がはかどるかどうかは、その使い方次第です。
class Shape: def paint(self, g): raise NotImplementedError, "def paint(self,g)"
クラスShapeは、インターフェースとして、子孫クラスに共通するプロトコルを規定します。メソッドpaintは、子孫クラスで『必須として』再定義する必要があり、そうしないと、実行時に例外NotImplementedErrorを生成します。
abstract/interfaceと違って、コンパイラによって強制されるものではありません。規則に縛られず、プログラマが慣習に従って自主的に用います。この関係を「強制と共生の文化の違い」と評する人もいます。paintでは、例外NotImplementedErrorを生成することで、プログラマの注意を促し、作業プロセスが適切に遂行されることを期待します。洗練されたフレームワークでは、作業成果(プロダクト管理)と、作業過程(プロセス管理)とは、コインの裏表と同様に統合されています。プロダクト指向からプロセス指向への扉を開く話題については、後の連載で紹介します。class Tile(Shape): def paint(self, g): self.paintBackground(g); self.paintItem(g); def width(self, g): return g.clipBounds.width/PuzzlePanel.dim def height(self, g): return g.clipBounds.height/PuzzlePanel.dim
クラスTileは、インターフェースShapeの規定に従って、メソッドを再定義します。
メソッドpaintは、各コマの背景paintBackgroundと前景paintItemを適切に描画します。
メソッドwidth/heightは、パネルの大きさが変化しても、各コマの大きさ(幅/高さ)を適切に保つために必要な情報を提供します。
絶景かな?
頂上から眺めると、見渡す限りの風景の中に、そこにたどり着くまでの道程も見えてきます。ソフトウェア開発でも、完成したプロダクトを眺めるだけでなく、そこにたどり着くまでのプロセスを俯瞰してみると、そこに新たな世界が開けて見えてきます。
PuzzlePanelは、Tileではなく、インターフェースを規定したShapeに依存しています。PuzzlePanelが保持するコマは、Tileだけとは限りません。Shapeで規定したプロトコルに従うなら何でもかまわないので、より柔軟性に優れたコードを記述できます。この話題については、後の連載で紹介します。
ここで着目して欲しいのは、Shapeを境に、表示の対象となるモデルと、表示を担当するビューとを分断したことです。モデルの立場からは、より見栄えのする表示をしたいものです。ビューの立場からは、同じボードを使って他のゲームを楽しみたいものです。この考え方は、MVCなどのフレームワークを構築するときにも役立ちます。

