SHOEISHA iD

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

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

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

Java meets Python - 第7回 ハリウッドの原則

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

  • X ポスト
  • このエントリーをはてなブックマークに追加

部品に分解する

 複雑なシステムを構築した後の、再利用可能な部品への分解は重要です。

再利用可能な部品へ分解
再利用可能な部品へ分解

 分解した部品を組み合わせて新たに別のシステムを構築できることから、既存のクラスを分割統治する作業は、部品に「分解」する行為と見なせます。同時に、抽象化を行うと、クラス間の継承による「汎化/特化」構造と、インスタンス間の委譲による「全体/部分」構造とを両立できます。

 この工程は「factorization」とも呼ばれ、プログラマーとしての技量が問われます。リファクタリング〔re-factoring〕という呼称は、先に紹介した全工程を包括する概念ですが、特にこの工程の重要性を象徴しているかのようです。

《Tips》プロダクト指向からプロセス指向へ
 リファクタリングを遂行すると、作業プロダクト(成果)を洗練するだけでなく、作業プロセス(行程)をも最適化します。プロダクト指向からプロセス指向への扉を開く鍵は、ここにもあります。その扉をいつ開くかは、読者の皆さん次第です。

イベント処理

 Swingが提供するフレームワークの典型がイベント処理です。マウスをクリックするとイベントが発生して、メソッドmouseClickedを起動します。必要であれば現在の表示を無効invalidateにして、ウィンドウ内を再描画repaintします。

イベント処理の流れ
イベント処理の流れ
#------ before --------------------------------
class GameBoardPanel(JPanel):
    def __init__(self):
        JPanel.__init__(self,
            mouseClicked=self.this_mouseClicked)
        ...
#------ after --------------------------------
class PuzzlePanel(GameBoardPanel):
    def this_mouseClicked(self, e):
        ...
        if tile.move(): self.repaint()
class LifeGamePanel(GameBoardPanel):
    def this_mouseClicked(self, e):
        ...
        self.repaint();
class OthelloPanel(GameBoardPanel):
    def this_mouseClicked(self, e):
        ...
        self.repaint()

 メソッドthis_mouseClickedは、イベントが発生した対象を適切に処理した後で、盤面を再描画repaintします。すると、Swingが提供するフレームワークに沿って、メソッドpaintComponentが呼応します(ハリウッドの原則)。この一連の流れは、局所的なコードの断片を見ただけでは分かりません。フレームワークを活用するには、設計者の意図を反映した暗黙の了解(権利/義務)を把握しておくことが重要です。

《Tips》機能継承からプロトコル継承へ
 例えば、read/writeの前には、openする必要があります。また、read/writeの後では、closeする必要があります。メソッド呼び出しの有効性を保証するには、シグニチャーを規定するだけでは不十分です。オブジェクト指向の恩恵を被るには、クラスが提供するインターフェイスの他に、有効なプロトコルを規定して、それを正しく伝承することが肝要です。さもないと、思わぬしっぺ返しが待っています。

事例:リファクタリング前

 具象クラスでは、視覚部品として、各アプリケーションに固有の機能を提供します。リファクタリングを実施する前に、その問題点を列挙します。

#------ before --------------------------------
class PuzzlePanel(JPanel):
    def __init__(self):
        self.initialize()
        self.initializeComponent()
        self.shuffle(1000)
    def initialize(self):
        self.items = []
        ...
    def initializeComponent(self):
        self.mouseClicked = self.this_mouseClicked
class LifeGamePanel(JPanel):
    def __init__(self):
        JPanel.__init__(self,
            mouseClicked=self.this_mouseClicked)
        self.initialize()
    def initialize(self):
        self.items = []
        self.locateLife()
        self.addNeighbor()
    def locateLife(self): ...
class GameBoardPanel(JPanel):
    def __init__(self):
        JPanel.__init__(self,
            mouseClicked=self.this_mouseClicked)
        self.initialize()
    def initialize(self):
        self.items = []
class OthelloPanel(GameBoardPanel):
    def __init__ (self):
        GameBoardPanel.__init__(self)
        self.mode = self.black
    def initialize(self):
        GameBoardPanel.initialize(self)
        self.locateStone()
    def locateStone(self): ...

クラス PuzzlePanel

 メソッド__init__は、親クラスJPanelで規定したプロトコルに従って、パネルを再構成します。しかし、親クラスをGameBoardPanelに変更すると、重複するメソッド呼び出しinitialize/initializeComponentを省略できます。

 メソッドinitializeの本体から、このクラスに固有のコードの断片を抽出して、メソッドlocateItemsとして再定義すると、後のリファクタリングを促進します。

 メソッドinitializeComponentは、イベントハンドラーmouseClickedに、関数オブジェクトthis_mouseClickedを保持します。しかし、親クラスをGameBoardPanelに変更すると、重複する操作を省略できます。

クラス LifeGamePanel

 メソッド__init__は、親クラスをGameBoardPanelに変更して省略できます。

クラス GameBoardPanel

 メソッド__init__は、ビュー(視覚部品)の特性を規定します。キーワード引数 mouseClicked=には、イベントハンドラーthis_mouseClickedを指定します。

 メソッドinitializeは、インスタンス属性itemsを初期設定します。クラス PuzzlePanel/LifeGamePanelGameBoardPanelの傘下に置くことで、重複するitemsを省略できます。

クラス OthelloPanel

 メソッド__init__は既に、親クラスGameBoardPanelで規定したプロトコルに従っているので、変更する必要はありません。

リファクタリングの実施
リファクタリングの実施

事例:リファクタリング後

 個より和を重んじるなら、フレームワークを導入するなりの価値があります。特定の機能を実現するだけなら(一見すると)冗長と思えるコードでも、他の実体との協調作業に貢献します。そうすることで再利用を促進すると共に、仕様の変更に伴うコストを低減して、柔軟性に優れたリソース管理が可能となります。

#------ after --------------------------------
class GameBoardPanel(JPanel):
    def __init__(self):
        JPanel.__init__(self,
            mouseClicked=self.this_mouseClicked)
        self.initialize()
    def initialize(self):
        self.items = []
        self.locateItems()
        self.settle()
    def locateItems(self):
        raise NotImplementedError, "def locateItems(self)"
    def settle(self): pass
class PuzzlePanel(GameBoardPanel):
    def __init__(self):
        GameBoardPanel.__init__(self)
        self.shuffle(1000)
    def locateItems(self): ...
class LifeGamePanel(GameBoardPanel):
    def locateItems(self): ...
    def settle(self):
        self.addNeighbor()
class OthelloPanel(GameBoardPanel):
    def __init__ (self):
        GameBoardPanel.__init__(self)
        self.mode = self.black
    def locateItems(self): ...

 抽象クラスGameBoardPanelは、共通するプロトコルを規定します。具象クラスを規定しないので再利用性が高く、仕様の変更にも柔軟に対処できます。すべての子孫クラスPuzzlePanel/LifeGamePanel/OthelloPanelは、GameBoardPanelの傘下に入ることで、共通するフレームワークの規定に従います。

 具象クラスでは、メソッド__init__は、GameBoardPanel.__init__で規定したプロトコルに従うと共に、固有の特性を実現します。ただし、LifeGamePanelでは、固有の処理が必要ないので、このメソッドを再定義しません。

 新たに抽出したメソッドlocateItemsは「必須」の存在です。具象クラスで固有の処理を履行するために、例外NotImplementedErrorを生成して、作業プロセスの監視(必要なコードが記述されていない…など、プログラマーに注意を促す)を行います。さらに、具象クラスの固有メソッドlocateLife/locateStoneを同じ名前locateItemsに統一することで、リファクタリングを促進します。

 新たに抽出したメソッドsettleは「任意」で、具象クラスで後処理をするために、その本体を空passにしています。LifeGamePanelでは後処理が必要になるので、addNeighborによって隣接する生命体を登録します。

《Tea Break》その知識が真の理解を妨げる
 あるメソッドの本体で、他のメソッドを呼び出すだけ…という場面に遭遇します。先の settleはその典型です。実行効率に配慮すると、一見無駄にしか思えないコードの真の価値を理解できずに損を蒙ります。その背景にフレームワークが潜んでいないか、探求してみるだけの価値はあります。私たちは、何か新しい知識を獲得した瞬間から、それを前提に物事を理解しがちです。しかし、それが行き過ぎるとその先にある知識を開く扉を永遠に閉ざしかねません。Weinbergさんの「金槌の法則」はその教訓となります。

次のページ
抽象クラスとインターフェイス

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

  • X ポスト
  • このエントリーをはてなブックマークに追加
よろずプログラマーのためのPython導入ガイド連載記事一覧

もっと読む

この記事の著者

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

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

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

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

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

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

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

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/2253 2008/06/17 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング