抽象クラスとインターフェイス
クラスには、抽象クラス/インターフェイスの二面性があります。Javaではその違いについて、 abstract class/interfaceの二者択一を迫られます。しかし、Python に限らず、洗練されたオブジェクト指向の支援が得られる環境下では、そのような制約は無用です。役割を任意に混在したり、宣言的に選択したり、動的スキーマを活用した柔軟な問題解決が可能です。
abstract class/interfaceで運命を決定付ける手法は(仕様の変更に迅速に対処したい)アジャイル開発では失速を余儀なくされる場面も少なくありません。未熟な言語仕様のツケをプログラマーが清算させられる様は、過去の歴史からも学べます。インターフェイスとして
先の連載では、クラスShapeをインターフェイスと見なして、子孫クラスに共通する特性を規定しました。
#------ before --------------------------------
class Shape:
def paint(self, g):
raise NotImplementedError, "def paint(self,g)"
class Tile(Shape):
def paint(self, g):
self.paintBackground(g);
self.paintItem(g);
class Life(Shape):
def paint(self, g):
self.paintItem(g)
class GameItem(Shape): ...
class Stone(GameItem):
def paint(self, g):
self.paintBackground(g)
self.paintItem(g)
メソッドpaintは、例外NotImplementedErrorを生成することで抽象メソッドとなり、具象クラスで個別の処理が履行されるように監視します。子孫クラスではインターフェイス Shape の規定に従って、これを定義する義務が生じます。
子孫クラスTile/Life/Stoneでは、メソッドpaintの本体でpaintItemが重複することが分かります。またクラスLifeでは、paintBackgroundを必要としません。リファクタリングを実施して、これらの問題点を解消します。
抽象クラスとして
クラスShapeをインターフェイスではなく、抽象クラスと見なします。
#------ after --------------------------------
class Shape(object):
def paint(self, g):
self.paintBackground(g)
self.paintItem(g)
#------ after --------------------------------
class GameItem(Shape): ...
class Tile(GameItem): ...
class Life(GameItem):
def paintBackground(self, g): pass
class Stone(GameItem): ...
抽象クラスShapeでは、メソッドpaintを(抽象メソッドではなく)テンプレートとして規定します。本体にあるメソッドpaintBackground/paintItemは、子孫クラスTile/Life/Stoneで実現します。ただしクラスLifeでは、メソッドpaintBackgroundを無効にするために、その本体をpassとします。
Shapeにインターフェイスの役割を託すなら、抽象クラスGameItemに何を分担しますか。その長所/短所について考察してください。
