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

分解した部品を組み合わせて新たに別のシステムを構築できることから、既存のクラスを分割統治する作業は、部品に「分解」する行為と見なせます。同時に、抽象化を行うと、クラス間の継承による「汎化/特化」構造と、インスタンス間の委譲による「全体/部分」構造とを両立できます。
この工程は「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さんの「金槌の法則」はその教訓となります。

