オブジェクト指向プログラミングを採用する大きなメリットは、開発したソフトウェアをコンポーネント(部品)として再利用できることでしょう。再利用性が高ければ、生産性向上が期待できます。
しかし、そもそもプログラムを再利用するとはどういうことでしょうか。既存のプログラムソースからコピーして利用することも再利用には違いありません。しかし、毎回そうするのは効率的ではありません。なんらかの形で再利用するプログラムを共有できるようにできれば、いちいち必要な部分をコピーする手間が省けます。
Tcl では、プログラムをパッケージ化してライブラリとして利用できます。オブジェクト指向とパッケージ化とは同じではありませんが、パッケージ化することでオブジェクト指向プログラミングの高い再利用性というメリットを享受しやすくなります。ここでは Tcl のスクリプトのパッケージ化を支援する機能を紹介し、前回のサンプルに応用してみます。
プログラムのパッケージ化
Tcl では、パッケージに関する操作を package コマンドでおこないます。Tcl プログラムでパッケージを読み込む場合は、
package require パッケージ名 ?バージョン番号?
のようにします。Tcl は変数 auto_path に格納されているパス内から指定されたパッケージを検索し、パッケージ名が存在すれば、パッケージがスクリプトであれば source コマンドで、バイナリの共有ライブラリであれば load コマンドで読み込みます。
変数 auto_path のデフォルト値は、Tcl がインストールされている場所に依存した値が格納されています。自分が使っている Fedora Linux 10 の RPM パッケージ、Tcl 8.5.3 では、次のようになっています。
$ tclsh
% puts $auto_path
/usr/share/tcl8.5 /usr/lib/tcl8.5 /usr/lib/tk8.5
%
TclOO がビルトインパッケージに組み込まれた Tcl/Tk 8.6 系はまだα版ですので、$HOME 内にインストールして使用しています。この場合は、次のようになっています。
$ tclsh8.6
% puts $auto_path
/home/bitwalk/lib/tcl8.6 /home/bitwalk/lib
%
$HOME 内にインストールされていれば、パッケージのインストールパスは、自分でプログラムの編集に利用できる領域ですが、Linux ディストリビューションから配布されているパッケージの場合は、/usr 以下の領域ですのでそうはいきません。
auto_path は、プログラム側で変更可能な変数です。そこで、適当な名前の環境変数を用意してプライベートなパッケージ領域のパスを設定し、Tcl 側の配列 env でその値を読み、変数 auto_path にそのパスを追加することにします。
環境変数を .bashrc に次にように追加しました。
export TCLPKG=$HOME/tclpkg
環境変数名は TCLPKG でなければならない、というわけではありません。しかし、本ブログでの説明では、今後、この環境変数名を使います。
ちなみに csh/tcsh をシェルに使っている場合は、.cshrc あるいは .tcshrc に、
setenv TCLPKG $HOME/tclpkg
と追加します。
Tcl では、配列 env で、この環境変数の値を利用できます。
$ tclsh8.6
% parray env
env(COLORTERM) = gnome-terminal
env(CVSROOT) = :ext:bitwalk@mingw-cross.cvs.sourceforge.net:/cvs...
env(CVS_RSH) = ssh
(中略)
env(TCLPKG) = /home/bitwalk/tclpkg
env(TERM) = xterm
env(USER) = bitwalk
env(USERNAME) = bitwalk
env(WINDOWID) = 48240455
(中略)
%
これで準備ができました。
前のサンプル・プログラム tkwidget_canvas_2.tcl で利用したクラス Draw の部分を分離してパッケージ化します。
まず、パッケージを格納する場所を決めましょう。$HOME/tclpkg 以下に Draw というディレクトリを作成しておきます。
$ mkdir ~/tclpkg
$ mkdir ~/tclpkg/Draw
サンプル・プログラム tkwidget_canvas_2.tcl から分離したプログラムは次のようになります。スクリプトの先頭にある package provide Draw 1.0 は、パッケージ名(Draw)とバージョン(1.0)を、呼び出す側のプログラムで実行した package require コマンドに(間接的に)知らせるためのコマンドです。パッケージ名は先頭を大文字にします。
# ----------------------------------------------------------------------------
# 拡張パッケージ Draw
# ----------------------------------------------------------------------------
package provide Draw 1.0
package require Tk;
package require TclOO; # ビルトインパッケージなので省略可
namespace import oo::*
# ----------------------------------------------------------------------------
# クラス Draw
# ----------------------------------------------------------------------------
class create Draw {
# ------------------------------------------------------------------------
# コンストラクタ
# ------------------------------------------------------------------------
constructor {} {
my variable conf
set conf(color) black
set conf(width) 1
}
# ------------------------------------------------------------------------
# 設定
# ------------------------------------------------------------------------
method init {color {width 1}} {
my variable conf
set conf(color) $color
set conf(width) $width
return
}
# ------------------------------------------------------------------------
# 描画起点の座標を取得
# ------------------------------------------------------------------------
method first {x y} {
my variable conf
set conf(x0) $x
set conf(y0) $y
return
}
# ------------------------------------------------------------------------
# 線を描画
# ------------------------------------------------------------------------
method line {w x y} {
my variable conf
$w create line $conf(x0) $conf(y0) $x $y \
-fill $conf(color) \
-width $conf(width) \
-tags mLine
my first $x $y
return
}
# ------------------------------------------------------------------------
# 線を消去
# ------------------------------------------------------------------------
method erase {w} {
$w delete mLine
return
}
}
# ---
# Draw.tcl
これを Draw.tcl というファイル名で、先ほど作成した ~/tclpkg/Draw に保存します。
次に、呼び出すプログラム側がパッケージを検索するのに必要なインデックスファイル pkgIndex.tcl を生成します。このファイルは、Tcl の pkg_mkIndex コマンドを使うと簡単に生成できます。
$ cd ~/tclpkg/Draw
$ ls
Draw.tcl
$ tclsh8.6
% pkg_mkIndex . *.tcl
% exit
$ ls
Draw.tcl pkgIndex.tcl
$
生成された pkgIndex.tcl は次のようになっています。
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded Draw 1.0 [list source [file join $dir Draw.tcl]]
これで Draw パッケージの作成完了です。
さて、クラスの部分を分離した本体の tkwidget_canvas_2.tcl(tkwidget_canvas_3.tcl にファイル名を変更)は次のようにします。
#!/bin/sh
# the next line restarts using wish \
exec wish8.6 "$0" "$@"
# 環境変数 TCLPKG が定義されていれば auto_path に追加
if {[info exist env(TCLPKG)]} {
lappend auto_path $env(TCLPKG)
}
package require Draw
# ----------------------------------------------------------------------------
# メイン
# ----------------------------------------------------------------------------
wm title . "canvas"
set color_bg "#f0fff8"
set color_grid "#c0e0d0"
set color_draw "#804040"
# キャンバスの生成
set cw 200
set ch 200
canvas .can \
-width $cw \
-height $ch \
-borderwidth 0 \
-highlightthickness 0 \
-background $color_bg
pack .can
# 方眼
for {set y 0} {$y < $ch} {incr y 10} {
.can create line 0 $y $cw $y -fill $color_grid
}
for {set x 0} {$x < $cw} {incr x 10} {
.can create line $x 0 $x $ch -fill $color_grid
}
# クラス Draw のインスタンス pen を生成
set pen [Draw new]
# メソッド init で描画色を設定
$pen init $color_draw
# マウス左ボタンを .can 上で押した時、メソッド first で座標を取得
bind .can <Button-1> {$pen first %x %y}
# マウス左ボタンを押しながら .can 上を移動した時、メソッド line を実行
bind .can <B1-Motion> {$pen line %W %x %y}
# ---
# tkwidget_canvas_3.tcl