Java.use(better, Python)《11》継承に警鐘を鳴らす(その捌)

記事一覧《こちらに移動中です》2006年6月19日 (月)

Java.use(better, Python)  # Stairway to Real Agile World

《11》継承に警鐘を鳴らす(その捌)《Jython2.5.0》

《著》後藤いるか・河野かえる・伊藤うさぎ・小粒ちゃん+∞《監修》小泉ひよ子とタマゴ倶楽部
第1版♪2003/05/23

■ 概要

継承の概念を実現するのに、いくつかの方法があります。そのひとつが、新しいクラスを定義した後で、そのインスタンスに固有のメソッドを再定義するというものです。これによって(2c)クラス-インスタンス間のメソッド継承が可能となり、要求仕様の変更に際して、クラス間のメソッド継承と比べて、より柔軟な対処が可能となります。これはアジャイル開発言語には欠かせない特徴のひとつです。

継承には、
 (1)構造継承
 (2)機能継承
 (3)プロトコル継承
があって、さらに(2)機能継承は、次の3つに分類されます。
 (2a)親子関係にあるクラス間の継承
 (2b)親子関係にないクラス間の継承
 (2c)クラスとインスタンス間の継承
ともすると、Java/C# などでは、狭義の(2a)に関心が寄せられがちです。しかし、純粋な OOP の世界では、その限りではありません。では、広義の継承は、どのように実現するのでしょうか。

■ 関連記事
  •  

前の記事次の記事

《11》継承に警鐘を鳴らす(その捌)

■ 二分木の例

単純な二分木を使って、インスタンスに固有のメソッドを定義する事例を紹介します。

t = Tree(0,Tree(1,Tree(2),Tree(3)),Tree(4,Tree(5),Tree(6)))

t.display()
print t
# -------------------------------- Output --
        +-- 2
    +-- 1
        +-- 3
+-- 0
        +-- 5
    +-- 4
        +-- 6

0(1(2(,),3(,)),4(5(,),6(,)))

クラス Tree は、二分木を実現したものです。Tree のインスタンスを生成して、変数 t に代入します。t は、いくつかの部分木によって構成されます。式 t.display() を評価すると、t の構造を表現した文字列が出力されます。文 print t を実行すると、t の文字列表現が得られます。

0 を値とする節を頂点(根)とする、二分木が得られます。頂点の左側には、1 を値とする節を頂点とする左部分木が存在します。頂点の右側には、4 を値とする節を頂点とする右部分木が存在します。2 を値とする節は、左右の部分木を持たない葉となります。3、5、6 についても同様です。

print "size:", t.size()
# -------------------------------- Output --
size: 7

式 t.size() を評価すると、二分木 t の大きさ(節の数)が得られます。出力結果を見ると、t には 7 つの節が存在するのが分ります。

t = Tree()
t.display()
# -------------------------------- Output --
+-- 0

コンストラクター Tree() を評価すると、新たな二分木 t が生成されます。式 t.display() を評価すると、t の構造を表現した文字列が出力されます。出力結果を見ると、0 を値とする節には、部分木がなく、これが葉であることが分ります。

class Tree:
  def __init__(self,value=0,left=null,right=null):
    self.value = value
    self.left  = left
    self.right = right

コンストラクター Tree() を評価すると、新たな二分木 t が生成されます。メソッド関数 __init__ は、インスタンス生成したときに呼び出されるものでした。コンストラクターを呼び出したときの実引数が、それぞれの仮引数と対応します。実引数を指定しないと、省略時の値が各引数に代入されます。

代入文 t = Tree() を実行すると、引数 right には 0 が代入されます。また、引数 left および value には null が代入されます。null については、後で解説します。これらの引数の値が、それぞれの対応するインスタンス属性に代入されます。

class Tree:
  def display(self):
    print self.displayString(0)

メソッド関数 display は、二分木の構造を表現する文字列を生成します。ここでは、下請けのメソッド関数 displayString を呼び出しているだけです。このとき、実引数には 0 を与えます。

class Tree:
  def displayString(self,n):
    return "%s%s%s" % (
      self.left .displayString(n+1),
      "%s+-- %s\n" % (" "*4*n, self.value),
      self.right.displayString(n+1))

メソッド関数 displayString は、display を補助するもので、他の目的で利用することはありません。仮引数 n は、二分木の深さを表します。左右の部分木 self.left および self.right に対して、displayString を再帰的に呼び出しています。このとき、実引数を n+1 とすることで、一段深い部分木を探索することになります。また、節 self.value の値を文字列の中に埋め込みます(間順走査)。

class Tree:
  def size(self):
    return 1 + self.left.size() + self.right.size()

メソッド関数 size は、二分木の大きさ(節の数)を獲得します。左右の部分木 self.left および self.right に対して、size を再帰的に呼び出すとともに、自身の節の数 1 を加えます。よって、部分木を持たない二分木の大きさは、1 となります。

※ display の出力を回転させると、左右が逆になります。縦の線で折り返しながら見てください。□

class Tree:
  def __str__(self):
    return self.reprString()

メソッド関数 __str__ を再定義すると、print 文などを使ってオブジェクトを表示するときの動作を規定できます。ここでは、下請けのメソッド関数 displayString を呼び出しているだけです。このとき、実引数には 0 を与えます。

class Tree:
  def reprString(self):
    return "%s(%s,%s)" % (
      self.value, self.left, self.right)

メソッド関数 displayString は、__str__ の補助するもので、他の目的で利用することはありません。インスタンス属性の値を埋め込んだ文字列を生成します。

《課題》特殊メソッド:__str__
 __str__(self)

オブジェクトの文字列表現を得ます。□

賢明な読者のみなさんは、ここで紹介したコードに、バグが繁殖する可能性を嗅ぎ付けたことでしょう。左右の部分木 self.left および self.right に対して安易にメソッド呼び出しを行うと、未定義エラーとなり、例外を生成しかねないからです。
しかし、その心配は杞憂に終ります。バグの繁殖を好まない、特殊なインスタンス null がそこに介在するからです。null は、Tree のインスタンスにはない、固有のメソッドを持つ突然変異で、他のインスタンスとは異なる振る舞いをします。インスタンスに固有のメソッドを定義するということは、遺伝子組み換えによる特殊な部分木を発生されたものと見なせます。
==================================
後藤いるか+伊藤うさぎ 著 ◆ 監修:小泉ひよ子とタマゴ倶楽部

更新♪2009/08/02