Python.use(better)《2》継承に警鐘を鳴らす(その弐)
‖記事一覧‖ Python.use(better)《Python3.1》
継承に警鐘を鳴らす(その弐)
オブジェクト指向プログラミング〔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活用ガイド―例題に学ぶオブジェクト指向プログラミング設計
- 作者: 小泉ひよ子
- 出版社/メーカー: カットシステム
- 発売日: 2004/01
- メディア: 単行本
- クリック: 3回
- この商品を含むブログ (7件) を見る
》こちらに移動中です《
↑TOP
関連記事
- Python への扉
- 2006-06-06 Java.use(better)《2》継承に警鐘を鳴らす(その弐)
|
8.2.1 では「属性アクセス」を話題にしているので、比較するのも一興です。◆ が、__getattribute__ を扱った事例はなく、期待した分だけ肩透かし…かしら。('ζ') ※ これに続く発展課題は、セミナーで紹介します。...Φ(^_^) |
□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□ | □ |