| OOPのメリット | FOPのメリット |
|---|---|
|
|
尚、オブジェクト指向の詳細については、専門書にお任せすることにします。
これ以降、OOPの専門用語が出てきますが、ご容赦ください。
オブジェクトを作成するには、インスタンス名を指定する方法と、
インスタンス名を自動的に生成する方法があります。
# オブジェクト作成1 クラス名 インスタンス名 # オブジェクト作成1 クラス名 #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で常に注意しなければならないのは、メンバの初期化忘れです。
メンバの初期化は必ず、宣言時に指定するかコンストラクタで行いましょう。
そうしないと不定値になってしまいます。
# 拡張カウンタ・クラス
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変数はそのクラスのインスタンス名を参照できます。
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}}
|
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
|
以下はスクロールバー付きのリストボックスの例です。
[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 |
# オプションの参照 .sl cget -borderwidth => 2 # オプションの設定 .sl configure -borderwidth 3 => 3 |
# 引数省略 .sl component => label listbox horizsb hull vertsb # 引数にコンポーネント名 .sl component label => .sl.label # コンポーネントのオプションの設定 .sl component listbox configure -background white |