公共の場、内緒の場
 今度のも機能面はほとんど同じですが、 要素が50個まで入るとそれ以上入らないようしきいを設けていますよ。
package require Itcl

class Moroqueue {
    private variable Q

    constructor {args} { set Q $args }

    private method isfilled {} {
        if {[llength $Q] >= 50} { return 1 } { return 0 }
    }

    public method enqueue e {
        if {[isfilled]} {
            error "Ootto! Queue size exceeds maximum."
        }
        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
    }
}

set m1 [Moroqueue mo#auto 5 6 7]
set m2 [Moroqueue mo#auto]
puts [$m1 get]
$m1 enqueue 4; puts [$m1 get]
$m1 enqueue 8; puts [$m1 get]
set a [$m1 dequeue]; $m2 enqueue $a
puts "[$m1 get] / [$m2 get]"
$m1 enqueue 7;
puts "[$m1 get] / [$m2 get]"
set a [$m1 dequeue]; $m2 enqueue $a
puts "[$m1 get] / [$m2 get]"
$m1 enqueue 8;
puts "[$m1 get] / [$m2 get]"
set a [$m1 dequeue]; $m2 enqueue $a
puts "[$m1 get] / [$m2 get]"
# end.

 メソッドやインスタンス変数の誤ったアクセスを防ぐため、 C++やJavaではメソッドやインスタンス変数に3種類のアクセス権限を設けることができます。 これすなわち、publicprotected およびprivateで、 [incr Tcl]でもこれらのキーワードと意味はまったく同じです。 例えば、上の例ではメソッド enqueue は public ですが、 isfilled は private です。つまり、クラスの外から、
  $m1 enqueue 8
はできますが、
  set r [$m1 isfilled]
とはできません。どのメソッドをprivateにし、 どのメソッドをpublicにしたらよいかというのは、 OOの超基礎で、C++やJavaやPythonと同じガイドラインに従ってください。 これに失敗するとせっかくのOOも水の泡なのです。

 ところで、あるクラスのインスタンス(オブジェクト)は、 同時に何個も作ることができますが、それらの名前(オブジェクトハンドル) を個別に作るのが面倒くさいときには、 [incr Tcl]に機械的につけさせることができます。

set m1 [Moroqueue mo#auto 5 6 7]
set m2 [Moroqueue mo#auto]
このように、オブジェクトハンドルの名前の一部に#auto を使うと、その部分が他のインスタンスとぶつからない適当な数字に置きかえられて Moroqueue「コマンド」の戻り値として帰ってくるので、 これをTcl変数にとっておき、以後のメソッド呼び出しは
$m1 enqueue 8
このようにすることもできます。 Tcl変数とオブジェクトハンドルがごっちゃになるとわけわか状態になるので、 普通はこのようにオブジェクトハンドルは必ず#autoを使い、 特に意識しない方がいいでしょう。

 おっと大事なことを忘れていました。public、protected、private のどれもつけない場合は、メソッドはpublic、変数はprivateがデフォルトです。


ボディの独立
 今度のMoroqueueは形が以前と大きく違いますが、機能はやっぱり同じです。
package require Itcl
class Moroqueue {
    private variable Q
    constructor args {}
    private method isfilled {}
    public method enqueue e
    public method dequeue {}
    public method get {}
}
body Moroqueue::constructor args { set Q $args }
body Moroqueue::isfilled {} {
    if {[llength $Q] >= 50} { return 1 } { return 0 }
}
body Moroqueue::enqueue e {
    if {[$this isfilled]} {
        error "Ootto! Queue size exceeds maximum."
    }
    if {[set p [lsearch -exact $Q $e]] != -1} {
        set Q [lreplace $Q $p $p]
    }
    lappend Q $e
}
body Moroqueue::dequeue {} {
    set r [lindex $Q 0]; set Q [lreplace $Q 0 0]
    return $r
}
body Moroqueue::get {} { return $Q }

 クラスが複雑になってくると、メソッドの数が増え、 また行数の多いメソッドも増えてきます。 するとこのクラスにはどんな変数とどんなメソッドがあるのかというのが非常に確認しづらくなります。 C++では、メンバ関数(いわゆるメソッド)とメンバ変数(インスタンス変数) だけをヘッダファイルに宣言として
class Cafe_au_leit {
    float mix(float coffee, float milk);
};
書いておき、 あとで.cppなどの本体を書くソースファイルに
float Cafe_au_leit::mix(float coffee, float milk){
    if(coffee > milk){
        this->palsweet += coffee - milk;
    }
    return coffee + milk;
}
こんな感じで書けますが、 上の例で出てきている通り、[incr Tcl]でも同様に
class Cafe_au_leit {
    method mix {coffee milk}
}

body Cafe_au_leit::mix {coffee milk} {
    if {$coffee > $milk} {
        incr palsweet [expr $coffee - $milk]
    }
    return [expr $coffee + $milk]
}
このようにbodyコマンドを使ってメソッドの定義を独立させることができます。 クラスの宣言(インターフェース)と定義(インプリメンテーション)を分離することで、 クラスの構造の見通しが非常によくなります。 ちなみに、
body Moroqueue::constructor args { set Q $args }
このようにコンストラクタも外に出すことができます。

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