SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

よろずプログラマーのためのPython導入ガイド

Java meets Python - 第4回 配列と別れる50の方法(2) オセロゲーム

よろずプログラマーのためのPython導入ガイド (6)


理想的な作業机

 同じ作業机を与えても、そこには個性が反映されます。机の上を整理整頓しておくと作業が捗るように、部品の構成管理にも配慮したいものです。

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は、子孫クラスで『必要なら』再定義することを想定しています。つまり、これらメソッドを自分で「呼び出す」ことよりも、他から「呼び出してもらう」ことに意義があります。フレームワークを有効に活用するには、この原則を理解するのが早道です。

《Tips》メソッドの権利と義務
 これらを抽象メソッドとしないのには、理由があります。つまり、メソッドを再定義するのは、子孫クラスの「権利」であって「義務」ではないことを意味します。必要がないなら「再定義しない」のも約束事の一つです。しかし、これらのメソッドを再定義するなら、差分プログラミングを支援することに意義があります。

理想的な部下

 子孫クラスには、理想的な部下のような存在であって欲しいものです。

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を自動的に呼び出します(ハリウッドの原則)。

  1. クリックした位置に石が存在するなら、何もせずに次のイベントを待ちます。
  2. メソッドreverseを利用して相手の石を裏返します。インスタンス属性reversedは、他の石を裏返したかどうかを記憶します。一つも裏返せなかったら、何もせずに次のイベントを待ちます。
  3. 新たに自分の石Stoneを生成して、盤面に配置placeします。
  4. 攻守を交代して、盤面を再描画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に委ね、盤面に配置したすべての石を再描画します。

次のページ
春うらら:コードを巡る旅の縁(よすが)に

この記事は参考になりましたか?

よろずプログラマーのためのPython導入ガイド連載記事一覧

もっと読む

この記事の著者

小泉ひよ子とタマゴ倶楽部(コイズミヒヨコトタマゴクラブ)

http://tamago-club.cocolog-nifty.com/「楽しくなければ仕事じゃない」が私たちのモットー。99%の苦悩の連続も、1%の成功に報われます。だからこそ、この仕事が楽しくて仕方がないのです。楽をするための努力なら惜しみません。何もせず楽をしているのと、努力をしたから楽ができるのと...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

伊藤うさぎ(イトウ ウサギ)

ペンネームの「由来は」と言うと。苗字の方は、セミナー研修で同じチームになった、3人の合体ユニット名 [I:石塚, T:田川, O:尾沢] から来ています。名前の方は、同じ干支(卯:1987 年生)に因んだものです。既に2人は卒業して、残る1人がその名跡を継承しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1802 2008/03/25 12:48

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー