Powered by [incr Tcl]

[incr Tcl]

[incr Tcl]は、Tclでオブジェクト指向プログラミング(以下OOPと略す)をするための拡張です。
Tcl8.4からTclコアまたはTclパッケージに統合されることになりました。
[incr Tcl]には50以上のMega-Widgetsを含む[incr Widgets]ライブラリが含まれています。
それらのWidgetはTkのWidgetのように動作し、[incr Tcl]やOOPの知識がなくても使えるようになっています。
また、[incr Tk]のフレームワークを使って、新しいMega-Widgetsを作ることができます。
まさに既存の部品を組み合わせて新しいものを作るというのはOOPのアプローチですね。

OOP vs. FOP

OOPと従来のファンクション指向プログラミング(FOP)の比較です。
OOPは恐らく間違いなくパフォーマンスの低下を招くと思われます。
しかしそのデメリットを補う以上のメリットがあるのも事実です。
どちらのアプローチがあなたにとってベストなのか考えてください。

OOPのメリットFOPのメリット
  • 抽象化
  • 情報のカプセル化(隠蔽)
  • クラスの派生(継承)
  • 保守性
  • 流用性
  • パフォーマンス

尚、オブジェクト指向の詳細については、専門書にお任せすることにします。
これ以降、OOPの専門用語が出てきますが、ご容赦ください。

クラス

Tclと[incr Tcl]はC言語とC++言語の関係に似ています。
[incr Tcl]のclassはC++のclassに似ています。
C++を勉強された方なら雰囲気で読めると思います。

オブジェクトを作成するには、インスタンス名を指定する方法と、
インスタンス名を自動的に生成する方法があります。

# オブジェクト作成1
クラス名 インスタンス名

# オブジェクト作成1
クラス名 #auto
ここでの#autoはコメントではありません。

クラスの定義とオブジェクトの生成の簡単な例を示します。
以下は乱数を発生させるクラスです。

package require Itcl
namespace import itcl::*

# 乱数クラス
class Random {
    # コンストラクタ定義
    constructor {} {
	init [expr [clock seconds]+[pid]]
    }
    # デストラクタ定義
    destructor {}
    # メソッド定義
    protected method init {value} {
	expr srand($value)
	return
    }
    # メソッド定義
    public method getvalue {} {
	expr rand()
    }
}

# オブジェクトの作成(インスタンス化)
Random r
=> r
# 乱数の発生
r getvalue
=> 0.482430962605
別の記述例

コンストラクタとは、オブジェクトの初期化を行うためのプロシジャです。
オブジェクトが生成された時に呼び出されます。
デストラクタとは、オブジェクトの後片付けを行うプロシジャです。
オブジェクトが破棄された時に呼び出されます(省略可能です)。
クラス内に定義されたプロシジャをメソッド(メンバ関数)と言います。
methodはアクセス・レベルがあることを除いてはprocと同じです。
メソッドの多重定義は、proc同様メソッドのデフォルト引数と可変引数argsによって実現可能です。
methodの代わりにprocを使うと、staticメソッドになります。

※OOPで常に注意しなければならないのは、メモリリークの対策です。
オブジェクトを作成して、破棄しないと確保したメモリは開放されません。
使い終わったら、オブジェクトを破棄しましょう。

# オブジェクトの破棄
delete object r

# クラスの破棄
delete class Random
コンストラクタ内でオブジェクトを作成した時は、デストラクタで破棄しましょう。

今度は、クラスにメンバを持つカウンタ・クラスです。

# カウンタ・クラス
class Counter {
    # メンバ宣言
    public variable count
    # コンストラクタ定義
    constructor {{value 0}} {
	set count $value
    }
    # メソッド定義
    public method incr {} {
	::incr count
    }
    # メソッド定義
    public method reset {} {
	set count 0
    }
}

# オブジェクトの作成(インスタンス化)
Counter c
=> c
# カウンタのアップ
c incr
=> 1
# カウンタ値の取得
c cget -count
=> 1
# カウンタのリセット
c reset
=> 0
# カウンタのセット
c configure -count 1

クラス内に定義された変数をメンバと言います。
メンバの仕様はアクセス・レベルがあることを除いてはvariableと同じです。
variableの代わりにcommonを使うとstaticメンバになります。

※OOPで常に注意しなければならないのは、メンバの初期化忘れです。
メンバの初期化は必ず、宣言時に指定するかコンストラクタで行いましょう。
そうしないと不定値になってしまいます。

派生(継承)

Counterクラスを派生(継承)させて、CounterExクラスを作ります。
前記Counterクラスとの違いはdecrメソッドを付加したところだけです。

# 拡張カウンタ・クラス
class CounterEx {
    # 派生(継承)
    inherit Counter
    # コンストラクタ定義
    constructor {{value 0}} {
	Counter::constructor $value
    }
    # メソッド定義
    public method decr {} {
	::incr count -1
    }
}

# オブジェクトの作成(インスタンス化)
CounterEx c
=> c
# カウンタのアップ
c incr
=> 1
# カウンタのダウン
c decr
=> 0
# カウンタ値の取得
c cget -count
=> 0

派生のメリットは、ベースクラス(基底クラス)を修正せずに、新たに機能を付加できることです。
派生したクラスには、ベースクラスとの差分のみを記述すればよいようになっています。
inherit文を複数記述すれば多重継承も可能です。

オーバーロード

クラスのメソッドは仮想メソッド(仮想関数)になり、オーバーロード可能です。
クラスを派生する際に、ベースクラスのメソッドと同じ名前のメソッドを定義できます。

# 親クラス
class Parent {
    # メンバの宣言
    protected variable _num
    # コンストラクタ
    constructor {{value 0}} {
	set _num $value
    }
    # メソッド
    public method print {} {
	puts Parent::$_num
    }
}

# 子クラス
class Child {
    # 派生(継承)
    inherit Parent
    # コンストラクタ
    constructor {{value 0}} {
	Parent::constructor $value
    }
    # メソッド
    public method print {} {
	Parent::print
	puts Child::$_num
    }
}

# オブジェクトの作成(インスタンス化)
Child c 3
# プリント
c print
=> Parent::3
=> Child::3
# 親クラス
class Parent {
    # メンバの宣言
    protected variable _num
    # コンストラクタ
    constructor {{value 0}} {
	set _num $value
    }
    # メソッド
    public method print {} {
	puts Parent::$_num
    }
}

# 子クラス
class Child {
    # 派生(継承)
    inherit Parent
    # コンストラクタ
    constructor {{value 0}} {
	chain $value
    }
    # メソッド
    public method print {} {
	chain
	puts Child::$_num
    }
}

# オブジェクトの作成(インスタンス化)
Child c 3
# プリント
c print
=> Parent::3
=> Child::3

この例では、printメソッドをオーバーロードしています。
左右の記述は若干異なりますが、結果は同じになります。
右の記述の方が、すっきりしています。

次に、メンバのオーバーロードの例を見てみましょう。

# 親クラス
class Parent {
    # メンバの宣言
    protected variable _num
    # コンストラクタ
    constructor {} {
	set _num 1
    }
    # メソッド
    public method print {} {
	puts $_num
    }
}

# 子クラス
class Child {
    # 派生(継承)
    inherit Parent
    # メンバの宣言
    protected variable _num
    # コンストラクタ
    constructor {} {
	set _num 2
    }
    # メソッド
    public method print {} {
	puts $_num
    }
}

# オブジェクトの作成(インスタンス化)
Child c 3
# プリント
c Parent::print
=> 1
# プリント
c Child::print
=> 2

ここで注意しなければならないのは、
メンバのオーバーロードは、親と子のクラスで別々に値のコピーを持つということです。

純粋仮想メソッド

純粋仮想メソッドを応用すると、抽象ベースクラスを作ることができます。

# 抽象ベースクラス
class Abstract {
    # コンストラクタ
    constructor {} {
	if {[namespace tail [$this info class]] == "Abstract"} {
	    error "抽象ベースクラスはインスタンス化できない"
	}
	if {![string length [$this info function pv -body]]} {
	    error "Abstractから派生したクラスでpvが定義されていない"
	}
    }
    # 純粋仮想メソッド
    protected method pv {} {}
}

抽象ベースクラスは必ず派生して使わなければエラーになります。
純粋仮想メソッドは派生したクラスでオーバーロードしなければエラーになります。
this変数はそのクラスのインスタンス名を参照できます。

スコープ

Tclは、イベントハンドラを使ってコマンドを実行することがあります。
afterコマンド、bindコマンド、button -commandコマンド等が該当します。
イベントハンドラはglobalスコープで実行される点に注意してください。
たとえば、以下の例は、afterで1秒後にコマンドが実行されますが、
コマンドが実行されるのはglobalスコープなので、変数varは未定義エラーになります。

class Hello {
    private variable var {Hello World}

    public method print {} {
        after 1000 {puts $var}
    }
}
Hello hw
hw print

解決策としては、codeとscopeコマンドでクラスを事前に解決したコードを実行する方法です。
codeコマンドを使うと、publicでないにプロシジャにアクセスできます。
scopeコマンドを使うと、publicでない変数にアクセスできます。

class Hello {
    private variable var {Hello World}

    private method do {a} {
        upvar $a v
        puts $v
    }
    public method print {} {
        set cmd [code $this do [scope var]]
        after 1000 $cmd
    }
}
Hello hw
hw print

この時、事前に解決したコード($cmd変数の内容)は以下の様になります。
inscopeコマンドは、指定されたnamespaceで引数のコマンドを実行します。

namespace inscope ::Hello {::hw do {@itcl ::hw ::Hello::var}}

生存期間

オブジェクトの生存期間には注意が必要です。
通常、作成したオブジェクトは、deleteコマンドで破棄するまで生存しています。
localコマンドを使って作成したオブジェクトは、現在のスコープを抜けると破棄されます。

class Foo {}

class Bar {
    constructor {} {
        set var [Foo #auto]
    }
}
Bar #auto
find object -class Foo
=>::Bar::foo0
class Foo {}

class Bar {
    constructor {} {
        set var [local Foo #auto]
    }
}
Bar #auto
find object -class Foo

localで作成したオブジェクトは破棄されているのがわかります。
localはテンポラリとしてオブジェクトを作成する場合に有効です。

[incr Tk]

[incr Tcl]には50以上のMega-Widgetsを含む[incr Widgets]ライブラリが含まれています。
[incr Widgets]は、[incr Tcl]のクラスによって実現されています。
[incr Tk]のフレームワークを使って、新しいMega-Widgetsを作ることができます。

以下はスクロールバー付きのリストボックスの例です。
[incr Widgets]は、TkのWidgetのように扱えます。

スクロールバー付きリストボックス

package require Iwidgets
namespace import iwidgets::*
pack [scrolledlistbox .sl -borderwidth 2] -fill both -expand 1
.sl insert end しょうゆ みそ しお
.sl selection set 0

cgetとconfigure

Tk同様、Widget作成後のオプションの参照と設定はcgetとconfigureを使います。

# オプションの参照
.sl cget -borderwidth
=> 2
# オプションの設定
.sl configure -borderwidth 3
=> 3

component

一方、Tkにはないcomponentコマンドがあります。
componentの引数を省略した場合は、はWidgetのコンポーネント名を返します。
引数にコンポーネント名を指定した場合は、そのコンポーネントのWidgetパスを返します。

# 引数省略
.sl component
=> label listbox horizsb hull vertsb
# 引数にコンポーネント名
.sl component label
=> .sl.label
# コンポーネントのオプションの設定
.sl component listbox configure -background white
※hullはMega-Widgets共通の外側のフレームのことです。

[incr Widgets]

[incr Tcl]には50以上のMega-Widgetsを含む[incr Widgets]ライブラリが含まれています。
詳細はMega-Widget Setを参照してください。


[incr Tcl]