ソフトウェア開発における輪廻転生
ハリウッドの原則に沿ってフレームワークを構築すると共に、今度はオブジェクト指向の立場からソフトウェア開発のライフサイクルを再考します。上図を縦方向に見ると、クラス間の関係(特殊化/抽象化)が変化する様子が見えてきます。横方向に見ると、インスタンス間の関係(合成/分解)が変化する様子が見えてきます。
そのライフサイクルは、伝統的なウォーターフォール型ではなく、スパイラル型のモデルによって説明されます。つまり「特殊化→合成→抽象化→分解」の過程を経て、成長を繰り返します。この過程はアプリケーション開発だけではなく、クラスライブラリー/フレームワークの開発にも見られます。生物が絶えず進化するのと同様に、クラスも進化/分化を繰り返します。このような観点から、先の連載で記述したコードには、どのような意義があるかを再考します。
部品を特殊化する
新しいクラスを定義する際に、その親クラスを選定は重要です。
既存のクラスが提供する特性を活用すると共に、不足する特性を追加/改良できることから、新しいクラスを導入する作業は、部品を「特殊化」する過程と見なせます。共通なものから固有なものを導出する様子は、クラス間の「継承」によって説明できます。親子関係で結ばれるクラスは「汎化/特化」構造を実現します。
Java の事情
Javaでは、クラス定義から始めます。
public class Tile { ... }
親クラスを省略すると、次のようにObject
を指定したものと見なされます。
public class Tile extends Object { ... }
事例:オブジェクトに固有の文字列表現
クラスTile/Life
を、GameItem
の傘下に置きます。すると、共通する特性を再利用すると共に、その違いだけを記述すればよくなります(差分プログラミング)。
#------ after -------------------------------- class GameItem(Shape): def __str__(self): return repr(self) def __repr__(self): return "(%i,%i)"%(self.x, self.y) class Tile(GameItem): def __repr__(self): return "%r%s"%(`self.value`, GameItem.__repr__(self)) class Life(GameItem): def __repr__(self): ... class Stone(GameItem): def __repr__(self): ... return "(%s,%s)"%(GameItem.__repr__(self), s)
メソッド__repr__
は、組み込み関数repr
の操作を規定して、オブジェクトに固有の「公式な」文字列表現が得られるようにします。可能であれば、「同じオブジェクトを生成するために有効なコード」を表わす文字列が得られるようにします。また、メソッド__str__
が未定義なら、オブジェクトに固有の「非公式な」文字列表現を得るためにも__repr__
が利用され、組み込み関数str
の操作を規定します。
親クラスGameItem
におけるメソッド__str__
は、その原理を説明するのためにあえて記述しましたが、通常は冗長なので省略できます。子孫クラスTile/Life/Stone
では、親クラスで定義したメソッドGameItem.__repr__
を再利用しています。
printOn:
/storeOn:
で規定して、printString
/storeString
で利用する」という暗黙の了解があります。JavaのtoString()
も事情は同じです。