継承とオーバーライド
 さあどんどんいきましょう。 今度は一度に1個しかデータをEnqueueできないMoroqueue に加え、複数の要素をEnqueueできるMomoroqueueというクラスを作ってみます。
package require Itcl
# Moroqueueは一番最初の簡単なサンプルの再掲です
class Moroqueue {
    protected variable Q
    constructor args { set Q $args }
    public method enqueue e {
        if {[set p [lsearch -exact $Q $e]] != -1} {
            set Q [lreplace $Q $p $p]
        }
        lappend Q $e
    }
    public method dequeue {} {
        set r [lindex $Q 0]; set Q [lreplace $Q 0 0]
        return $r
    }
    public method get {} { return $Q }
}

class Momoroqueue {
    inherit Moroqueue

    # コンストラクタで明示的にスーパークラスのコンストラクタを
    # 呼ぶ必要があります。
    constructor args {
        eval Moroqueue::constructor $args
    } {

    }

    public method clear {} { set Q {} }
    public method enqueue args {
        foreach e $args { Moroqueue::enqueue $e }
    }
}

Momoroqueue mo 5 6 7
puts [mo get]
mo clear
mo enqueue 4 7 2; puts [mo get]
mo enqueue 8 5; puts [mo get]
mo dequeue;   puts [mo get]
mo enqueue 7; puts [mo get]
mo dequeue;   puts [mo get]
mo enqueue 8; puts [mo get]
mo dequeue;   puts [mo get]
# end.

 あるクラスの構造を受け継いだまま、それに新しいメソッドを書き加えたり、 一部のメソッドの機能を少しだけ変えたりして新しいクラスを作り出すことを 継承(inheritance) といい、OO世界に踏み込んだときに出会う最も重要で、最も難しい敵のひとつです。 [incr Tcl]でもC++やPythonのような継承を実現しています。 上の例では、MomoroqueueクラスがMoroqueueクラスを継承しています。 これは、Momoroqueueクラスの最初の行に
  inherit Moroqueue
とあるように、inheritというコマンドで宣言します。 継承元のクラスを基底クラス(base class=基本クラスなどとも呼ばれます)、 継承してできたクラスを導出クラス (derived class=派生クラスなどとも呼ばれます)といいます。 継承されると、導出クラスには基底クラスのpublicまたはprotectedがついているメソッドが自動的に含まれます。 この例では、MomoroqueueクラスにはMoroqueueクラスの enqueue、dequeue、getなどのメソッドが引き継がれてやってきます。

 基底クラスから継承されてきたのと全く同じ名前のメソッドを導出クラスで新しく定義しなおすこともできます。 これはオーバーライドと呼ばれますが、 [incr Tcl]でも単に基底クラスのメソッドと同じ名前のメソッドを定義するだけでオーバーライドが行われます。 上の例では、enqueueメソッドをオーバーライドすることで、 複数の要素を一度に追加できるようにしています。

 他のクラスを継承して作ったクラスのコンストラクタは、 引数を2つとることに注意が必要です。最初の引数の中では、 基底クラスのコンストラクタをどんな引数で呼び出すかを指定し、 2つめの引数の中でこのクラス独自の初期化処理を記述します。

    constructor args {
        eval Moroqueue::constructor $args
    } {

    }
これは、C++の
    Momoroqueue::Momoroqueue(char* args) :
        Moroqueue(args)
    {

    }
という形にとてもよく似ています。

 ところで、メソッドのオーバーライドはできますが、 [incr Tcl]では、メソッドのオーバーロード (同じ名前で引数の違う複数のメソッドを定義すること) はできません。これは、Tcl言語のプロシージャが

  • 引数にデフォルトの値を指定することができる
  • 数が不定の引数「args」を使うことができる
という柔軟な仕様になっているため、 これで十分オーバーロードと同じ役目を果たすことができるからです。 オーバーライドは常に基底クラスの同名のメソッドを完全に上書きするわけですが、 「クラス名::メソッド名」とすれば、 Javaの「super」のように、基底クラスのメソッドを明示的に呼び出すこともできます。

 また、C++では、BというクラスがAというクラスを継承したとしたとき、

  B* bp = new B();
  A* ap = (A* ) bp;
  ap->method1();
  ap->method2();
このように基底クラスの型でメソッドを使ったとき、 クラスAとクラスB、どちらのクラスのメソッドが使われるかは、 メソッドがヴァーチャル(virtual)かどうか (生成時のクラスのメソッドを使うのがヴァーチャル、 現在のクラスのメソッドを使うのが「通常」)で決まりますが、 変数に「型」がない[incr Tcl]では、
  set a [A xxx#auto]
  $a method1
  set a [B xxx#auto]
  $a method1
変数「$a」に代入されたインスタンスのメソッドmethod1がどのクラスのメソッドかは、 常にそのインスタンスが生成されたときのクラスで決まります。 …ということは、[incr Tcl]、あるいは一般に変数に「型」がないOO言語のメソッドは、100%ヴァーチャルということになります。

拡張レビュー分室 top
(first uploaded 1999/10/28 last updated 2000/11/18, EK)