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 |