理想的な作業机
同じ作業机を与えても、そこには個性が反映されます。机の上を整理整頓しておくと作業が捗るように、部品の構成管理にも配慮したいものです。
class OthelloPanel(GameBoardPanel):
dim = 8
black = False
white = not black
def __init__ (self):
GameBoardPanel.__init__(self)
self.mode = self.black
def initialize(self):
GameBoardPanel.initialize(self)
self.locateStone()
クラスOthelloPanelは、抽象クラスGameBoardPanelが提供するフレームワークに沿って、アプリケーションに固有の視覚部品を実現します。
クラス属性dim/black/whiteは、インスタンスに共通する情報(定数項)を規定します。ただし、要求仕様に変更があると、この箇所が問題となるでしょう。
メソッド__init__は、親クラスGameBoardPanelで規定したプロトコルに従って、パネルを再構成します。インスタンス属性modeは、表になっている面の情報(黒/白)を管理します。黒が先手なので、初期値にはblackを設定します。
メソッドinitializeは、抽象クラスGameBoardPanelで規定したフレームワークに沿って、自動的に呼び出されます(ハリウッドの原則)。視覚部品に固有の特性を規定するもので、先に示した方法で石を配置locateStoneします。
理想的な上司
親クラスには、理想的な上司のような存在であって欲しいものです。
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,
size=size, title=title)
def initializeComponent(self): pass
クラスDefaultFrameについては、先の連載で紹介しました。ここでは、フレームワークを提供する立場から、ハリウッドの原則に沿って再考します。
クラスDefaultFrameは、抽象クラス(理想)として、子孫クラスに共通する特性を規定します。メソッド__init__は、そのテンプレートとして、共通するフレームワークだけを規定します。ここで、initialize/initializeComponentは、子孫クラスで『必要なら』再定義することを想定しています。つまり、これらメソッドを自分で「呼び出す」ことよりも、他から「呼び出してもらう」ことに意義があります。フレームワークを有効に活用するには、この原則を理解するのが早道です。
理想的な部下
子孫クラスには、理想的な部下のような存在であって欲しいものです。
class OthelloFrame(DefaultFrame):
def initialize(self):
self.panel = OthelloPanel()
def initializeComponent(self):
self.layout = BorderLayout()
self.add(self.panel, BorderLayout.CENTER)
クラスOthelloFrameは、具象クラス(現実)として、親クラスDefaultFrameで規定した特性を再定義したり、実質的な作業を他に委ねたりします。
先の連載では、クラスPuzzle15Frameにおいて、__init__を再定義しましたが、これは冗長です。なぜなら、__init__は、親クラスから継承されるからです。そのため、自分は何もしないで、DefaultFrame.__init__に委ねるだけなら不要です。Javaに限らず、コンストラクターを継承しないプログラミング言語とは、その対処法が異なるので、注意が必要です。
メソッドinitializeでは、そのクラスに固有の特性を規定します。インスタンス属性panelは、フレーム内に配置するパネルOthelloPanelを保持します。しかし、特定のクラスをメソッド本体に記述すると、柔軟性を損ないます。この問題を回避する方法については、後の連載で紹介します。
メソッドinitializeComponentでは、ビュー(視覚部品)の特性を規定します。指定したレイアウトBorderLayoutに従って、パネルを中央BorderLayout.CENTERに追加addします。
このように、フレームワークを有効に活用するには、設計者の意図を反映した約束事(権利/義務)を把握しておくのがコツです。
イベント処理
何かと厄介なイベント処理ですが、うまく付き合えば強力な味方となります。
class OthelloPanel(GameBoardPanel):
def this_mouseClicked(self, e):
stone = self.detectStone(e)
print ">>> %s"%stone
if stone: return
self.reversed = False
self.reverse(stone)
if not self.reversed: return
self.place(Stone(stone.x, stone.y, self.mode))
self.mode = not self.mode
self.repaint()
メソッドthis_mouseClickedは、マウスでクリックした場所に石を配置します。ここでは、detectStoneを利用して、イベントが発生した石を特定します。自分と同じ色の石で挟まれた相手の石を反転reverseさせ、盤面を再描画repaintします。このとき、Swingで規定したフレームワークに従って、メソッドpaintComponentを自動的に呼び出します(ハリウッドの原則)。

- クリックした位置に石が存在するなら、何もせずに次のイベントを待ちます。
- メソッド
reverseを利用して相手の石を裏返します。インスタンス属性reversedは、他の石を裏返したかどうかを記憶します。一つも裏返せなかったら、何もせずに次のイベントを待ちます。 - 新たに自分の石
Stoneを生成して、盤面に配置placeします。 - 攻守を交代して、盤面を再描画
repaintします。
class OthelloPanel(GameBoardPanel):
def reverse(self, stone):
bounds = [(x, y)
for x in range(-1, 2)
for y in range(-1, 2)]
for x, y in bounds:
e = self.detect(stone.x+x, stone.y+y)
if e.state == (not self.mode):
self.reverseStones(e, x, y)

メソッドreverseは、指定した石stoneで挟まれる相手の石eを裏返えします。挟まれた石が違う色e.value<>self.modeなら、eと同じ方向にある石を反転reverseStonesします。
class OthelloPanel(GameBoardPanel):
def paintComponent(self, g):
self.paintItems(g)
メソッドpaintComponentは、Swingで規定したフレームワークに従って、repaintを呼び出したときに起動されます(ハリウッドの原則)。そのため、任意のコンポーネントを再描画したいときには、このメソッドを再定義しておきます。実際の処理は補助関数paintItemsに委ね、盤面に配置したすべての石を再描画します。
