部品に分解する
複雑なシステムを構築した後の、再利用可能な部品への分解は重要です。
分解した部品を組み合わせて新たに別のシステムを構築できることから、既存のクラスを分割統治する作業は、部品に「分解」する行為と見なせます。同時に、抽象化を行うと、クラス間の継承による「汎化/特化」構造と、インスタンス間の委譲による「全体/部分」構造とを両立できます。
この工程は「factorization」とも呼ばれ、プログラマーとしての技量が問われます。リファクタリング〔re-factoring〕という呼称は、先に紹介した全工程を包括する概念ですが、特にこの工程の重要性を象徴しているかのようです。
イベント処理
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
が呼応します(ハリウッドの原則)。この一連の流れは、局所的なコードの断片を見ただけでは分かりません。フレームワークを活用するには、設計者の意図を反映した暗黙の了解(権利/義務)を把握しておくことが重要です。
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
/LifeGamePanel
をGameBoardPanel
の傘下に置くことで、重複する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
によって隣接する生命体を登録します。
settle
はその典型です。実行効率に配慮すると、一見無駄にしか思えないコードの真の価値を理解できずに損を蒙ります。その背景にフレームワークが潜んでいないか、探求してみるだけの価値はあります。私たちは、何か新しい知識を獲得した瞬間から、それを前提に物事を理解しがちです。しかし、それが行き過ぎるとその先にある知識を開く扉を永遠に閉ざしかねません。Weinbergさんの「金槌の法則」はその教訓となります。