抽象クラスとインターフェイス
クラスには、抽象クラス/インターフェイスの二面性があります。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
に何を分担しますか。その長所/短所について考察してください。