Python.use(better)《2》継承に警鐘を鳴らす(その弐)

記事一覧 Python.use(better)《Python3.1》

継承に警鐘を鳴らす(その弐)

《著》後藤いるか・伊藤うさぎ・小粒ちゃん+∞《監修》小泉ひよ子とタマゴ倶楽部
第1版♪1988/05/23 ● 第2版♪2001/01/29 ● 第3版♪2009/12/15

オブジェクト指向プログラミング〔OOP〕の基本概念の理解を深めます。
※ JPython1.1/Jython2.2 で作成した例題を、Python3.1 で再構成しました。


継承に警鐘を鳴らす(その弐)

クラス継承とメソッド委譲とを比較して、その問題点を検証します。

■ クラスを継承する

継承による実現は「ホワイトボックス」型の再利用とも呼ばれ、継承元のクラスの存在が露呈します。すると、クラスを実現する方法(継承)を逆手に取って「情報隠蔽の原則」を無視した操作が許されます。

    class Stack(list):
        def push(self, item):
            self.append(item)
        def pop(self):
            try:
                return super().pop()
            except IndexError as error:
                print("IndexError:", error)

>>> s = Stack(); s
[]
>>> s.push("A"); s
['A']
>>> s.push("B"); s
['A', 'B']
>>> s.push("C"); s
['A', 'B', 'C']
>>> s.remove("B"); s    # (x_x) 不当な操作を許す
['A', 'C']

たとえば、継承元の list が提供するメソッド remove を利用すると、任意の要素を取出せます。これは、スタックの仕様を無視した、不当な操作と見なせます。

■ メソッドを委譲する

委譲による実現は「ブラックボックス」型の再利用とも呼ばれ、委譲先のクラスの存在を隠蔽します。すると、委譲先のクラスとは独立した管理が可能になり「情報隠蔽の原則」を無視した操作を許しません。

    class Stack(object):
        def __init__(self):
            self.items = 
        def __repr__(self):
            return repr(self.items)
        def push(self, item):
            self.items.append(item)
        def pop(self):
            try:
                return self.items.pop()
            except IndexError as error:
                print("IndexError:", error)

>>> s = Stack(); s

>>> s.push("A"); s
['A']
>>> s.push("B"); s
['A', 'B']
>>> s.push("C"); s
['A', 'B', 'C']
>>> s.remove("B"); s    # ('o') 不当な操作を許さない
AttributeError: 'Stack' object has no attribute 'remove'

たとえば、委譲先の list が提供するメソッド remove では、任意の要素を取出せません。つまり、スタックの仕様を無視した、不当な操作を拒否します。

■ 何が問題か

一見すると、委譲は、継承に比べて記述すべきコードが増えるかのようです。実際には、スタックに対する不当な操作ができないように、継承に比べて(list には有効なメソッドの大半を禁じるために)はるかに多くのコードを追記する必要があります。では「委譲」は善で「継承」は悪なのでしょうか。必ずしもそうとは限りません。そこで着目したいのが「特殊メソッド」の存在です。

■ 特殊メソッド __getattribute__ を利用する

特殊メソッド __getattribute__ を利用すると、クラスの実現方法(継承元の list)に依存する部分を制限して、不当な操作を回避できます。

    class Stack(list):
        def push(self, item):
            self.append(item)
        def pop(self):
            try:
                return super().pop()
            except IndexError as error:
                print("IndexError:", error)

        def __getattribute__(self, name):
            attributes = "push", "pop", "append",
            if name in attributes:
                return list.__getattribute__(self, name)

>>> s = Stack(); s
[]
>>> s.push("A"); s
['A']
>>> s.push("B"); s
['A', 'B']
>>> s.push("C"); s
['A', 'B', 'C']
>>> s.remove("B"); s    # ('o') 不当な操作を許さない
TypeError: 'NoneType' object is not callable
>>> s
['A', 'B', 'C']
>>> s.append("D"); s    # (=.=) ただし問題は残る
['A', 'B', 'C', 'D']

生成したスタック s は、__getattribute__ 内で規定したメソッド push および pop を利用できます。しかし、メソッド remove は、この規定に含まれないので利用できません。

《note》:この戦略は、委譲の代替案としては不完全です。なぜなら、継承元 list のメソッドの一部(ここでは append)を制限できません。しかし、それ以外のメソッドの利用を制限できるので、best ではないものの、better なアプローチが可能になります。さらに、この問題を解消する方法は「基礎編」で紹介します。

■ 参考文献

JavaプログラマのためのUML活用ガイド―例題に学ぶオブジェクト指向プログラミング設計

JavaプログラマのためのUML活用ガイド―例題に学ぶオブジェクト指向プログラミング設計

》こちらに移動中です《
TOP


関連記事

Python 3 プログラミング徹底入門

Python 3 プログラミング徹底入門

  • 作者: マーク・サマーフィールド,Mark Summerfield,長尾高弘
  • 出版社/メーカー: ピアソン桐原
  • 発売日: 2009/12/01
  • メディア: 単行本
  • 購入: 1人 クリック: 61回
  • この商品を含むブログ (21件) を見る
8.2.1 では「属性アクセス」を話題にしているので、比較するのも一興です。◆ が、__getattribute__ を扱った事例はなく、期待した分だけ肩透かし…かしら。('ζ')
※ これに続く発展課題は、セミナーで紹介します。...Φ(^_^)
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

Last updated♪2010/03/09