浩http://www.st.rim.or.jp/~h_takeSmalltalkのMVCモデルが紹介されてから、20年が経過しているが未だにSmalltalk以上の優れた実装を実感したことがなかった。Programming"に紹介されている例をjavaで実装してみる。毎日短時間しかプログラミングができない私にとって、少しずつ動作を確かめながらプログラムを作りたいという願いがある。小さなプログラムでも動けば励みになるし、動作のチェックもできる。「ソフトウェア作法」でも、プログラムの骨格から作りはじめ、テストを繰り返しながら完成させていくスタイルが紹介されている。ユーザインタフェースを伴うプログラムではGUI領域のプログラムと問題解決領域のプログラムを切り分けることが難しい。SmalltalkのMVCでは、これをスマートに解決している。Programming"のカウンタ問題である。カウンタのイメージを図[カウンタのポンチ図]に示す。(1)一口にカウンタと言っても、いくつかのバリエーションが考えられる。カウンタ問題では、少し強引な感じはあるが次の3種類を例題として挙げている。問題領域での段階的プログラミング段階的にプログラミングすることとオブジェクトの性質を抽出することは同じではないが、OOPにおいてはオブジェクトの所有する本質的な部分を最初に実装し、その後に修飾部分を実装するのが一般的なプログラミングスタイルである。Javaを使って、このカウンタ問題を解きながら、現在のJavaの環境と開発スタイルについて整理してみる。値リセット値(クリアしたときの値)が考えられる。加算(increment)減算(decrement)リセット(reset)がある。このようなカウンタが持つべき概念的な属性や機能はアブストラクト・クラスとして定義する。節[整数カウンタ(IntegerCount)]は、アブストラクト・クラスの機能を実装したサブクラスとなる。カウンタクラスの構成を図[カウンタクラス構成]に示す。decrementは下位クラスで実装が必要なアブストラクトメソッドであるため、イタリックで表示されている。decrementメソッドを定義している。整数カウンタのUML図を図[カウンタクラス構成]に示す。(2)カウンタクラス(Count)カウンタクラスをjavaで記述したのが、リスト[Count]である。CountresetValueは、クラスで1個決まった値を持つものなので、staticでクラス変数として宣言した。整数カウンタ(IntegerCount)次に、整数カウンタクラスをjavaで記述する。IntegerCountどうしても汎用的なクラスにするために、整数値を内部データ型ではなく、Integerクラスを使ってラップしたために、加算と減算の手続きに少し無理がある。1);のようにvluae_を(Integer)にキャストして、intValue()でその整数値を取得し、1足した後、その値を整数値として持つIntergerオブジェクトを生成してvalue_にセットしている。前に私の環境について、簡単に説明する。2.2.6.1できるだけ、マシンとかOSに依存しない環境にしたいと考え、マウスとGUIが中心の時代に、Emacsというキャラクタペースの環境を使用することにした。Windows98上で動作するEmacsとしてMeadowがあり、Emacs上の開発環境としてJDEがあることをしったのが、このページを書く契機である。JDEは、すべてフリーであり、自由に利用することができる。JDEには、beanshell(以下bshと記す)というjavaのインタプリタがあり、先ほど作成したIntegerCounterをメインプログラムを作らずに単体で動作確認をすることができる。%と表示されている行がコマンド入力行で、その後の行が出力行である(説明文を//の後に付けている)。キー入力が多く、かえってメインプログラムを書いた方が楽なように見えるが、JDEと密接に連携しているため、クラス名や変数名の補完とEmacsのシェルモードが利用できるので、キー入力はそんなに多くはならない。16進カウンタ次に16進カウンタを作ってみる。16進カウンタと言っても整数のカウント値をどう見せるかと言う問題である。resetのメソッドは全く同じである。そこで、IntegerCountを継承して、16進カウントを作ることにした。16進数で表示するにはtoStringメソッドを更新する。これまで、IntegerのインスタンスをValue_にセットしてきたが、これはIntegerというラッピングクラスによって整数値を文字列に変換するのを汎用化するためである。定義していることを明記するために定義している。HexCountでは、bshを使ってできたてのHexCountを実行してみる。bshでは、手順をスクリプトとしてまとめて記述したり、独自の関数を定義してスクリプトを構造化することができる。そこで、テスト処理の一つのファイルhexTest.bshというファイルにまとめて実行する。例のように単なる実行文の羅列ではなく、オブジェクトの状態に応じて制御を変えることができるため、複雑なテストをbsh内で実施することができる。source("hexTest.bsh");0x00xC0xB0x0となる。カレンダカウンタカウンタの最後の例題としてカレンダカウンタを実装する。APIからそれらしいクラスを検索してみる。それらしいDateクラスが見つかったので、bshで動作を調べてみる。2001しかし、Dateクラスでは、日付のカウントができない。Calendarクラスには、addメソッドが用意されおり、日付の増減が可能であり、今回の目的に合致する。Calendarから日付を取り出すには、getTimeメソッドを使うことにする。試しにbshで動作を確認してみる。APIの説明からカット&ペーストするだけでAPIの動作を確認することができるのもbshの利点である。2001カレンダカウンタは、次のようになる。CalendarCountカウンタの値には、Date型を使い、日付のカウンタにCalendar型を使用する。ここでbshで実行してみる(説明のために実行文の後にコメントを付加した)。2001これで、問題領域での実装と動作確認は終了した。GUIとの結合の前に問題領域でのカウントの例題にGUIを追加する前に、bshを使ってGUIを追加する方法を紹介する。値とボタンを含むフレームを作ってみる。このフレームを表示するbshスクリプトは次のようになる。print(m);のようになる。"のように動的にオブジェクトを変更することができる。これがインタプリタの大きな特徴である。以上のように、javaのインタプリタを使うことによってテストに要する時間が短縮され、テスト方法の自由度も増すことが検証できた。まとめ問題領域の段階的プログラミングとして、ここでは段階的プログラミングにより、実際にプログラムの動作をテストしながら、プログラムを進化させることができるJDEのjavaインタプリタbshを使うことによってテスト行程が短縮できる不要なテストプログラムを作成しなくてもよいことを検証できた。問題領域で作成したクラスの構成をUMLで記述する。作成したプログラムの構造や技術的なポイントを後ですぐに取り戻すことができるように整理することが大切である。(4)このUML図は、フリーのソフトmUMLを使って作成した(http://www.mfcomputers.com/)。(後でUMLの早見表を入れる!)バージョンや入手方法を後で追加する予定。問題領域とGUI領域のプログラミングを分離できる1つのモデルを様々な形式で表示する事ができるモデルの動的な変更にビューが対応できるがある。MVCを使わないGUIカウンタ最初にGUIカウンタのGUIインタフェースを決めることにする。図[カウンタのGUIインタフェース]のようにresetボタン、incrementボタン、decrementボタンと整数の値を表示するテキストボックスから構成される。MVCを使ったGUIカウンタを作る前に、通常のGUIカウンタを作ってみる。リスト[CountDemo]がGUIカウンタのメインプログラムである。フレームを表示する}}IntegerCountのインスタンスをモデルとして作成し、それをCountViewに渡してビューを作成する。後は、おきまりのウィンドウを閉じるためのWindowAdapterを登録してフレームを表示する。次に、CountViewをリスト[CountView]に示す。JButton(resetAction);のようにAbstractActionを無名クラスとして定義し、JButtonのコンストラクタに渡す。これで、ボタンを押したときの動作を登録する。1.3で新たに追加された機能である。この機能によってボタンのコールバックがとてもきれいに記述することができる。resetボタンが押されたときにモデルにresetをコールし、モデルの値をvalueBoxにセットする。本来リセットボタンの機能(5)としては、ボタンを押された時にモデルにresetコールを実行することであり、値の再表示をどのように行うかはビューの機能である。これがMVCを使っていないプログラムの欠点である。バリューモデルの実装GUIカウンタ問題をMVCを使って実装するために、Javaのモデルクラスを調べてみた。JavaのAPIを探しても、値だけを持つシンプルなモデルは見つからない。(6)そこで、値のみを持つモデルを独自に定義することにする。p110-119"を元ネタとする。モデルインタフェースモデルは、インタフェースの定義とその実装に分かれる。値モデルのインタフェースをに示す。独自のイベントを定義し、イベントへのアクセスメソッドを規定しているイベントの受け手が実装するListenerインタフェースを規定している値属性のアクセスメソッドの定義とイベントリスナー関連のメソッドを規定しているである。javaでは、interfaceを使ってうまくMVCを構築している。処理する部分をinterfaceに記述している点に注意。内部イベントクラスを使って固有の情報をリスナーに渡せるようにしている。イベントの発行元をセットするためにsuper(model)をコール。次に値モデルのデフォルトモデルクラスを実装する。メンバーlisteners_に値モデルのリスナーをVectorとして登録する。removeListenerでリスナーを管理し、モデルの根幹である値をメンバー変数value_に保持する。value_へのアクセスメソッドgetValueはそのままvalue_の値を返すだけだが、setValueでは値をセットした後にリスナーに値が変化したことを通知するためにfireValueChangedを呼び出す。リスト[DefaultValueModel]にデフォルトモデルクラスのリスト示す。DefaultValueModelMVCを使ったGUIカウンタこれで、MVCを使ったGUIカウンタを実装する準備が整った。整数カウンタをMVCで実装するとどのようになるか試してみる(リスト[MVC版Count]参照)。Count最初にCountクラスの変更部分を見てみる。{でCountクラスをDefautValueModelのサブクラスとして定義する。次にresetメソッドをsetValue(resetValue_);のように直接value_にセットするのではなく、setValueメソッドを使うようにする。これがモデルクラスを使うときのポイントである。decrementメソッドでvalue_にセットしていたところをsetValueメソッドを使って値を設定する(リスト[MVC版IntegerCount]参照)。IntegerCountビューの部分の変更は、MVCモデルのビューを作るときの基本形になるところであり、値モデルのリスナーのアクションメソッドを定義する。{valueBox.setText(e.getValue().toString());}};モデルの値が更新されてイベントがリスナーに届いたら、ここで定義したvalueChangedが呼び出される。最後に、ビューのインスタンス生成時にモデルのリスナーに自分自身を登録します。model_.addListener(modelListener_);メインプログラムは同じなので、これでMVCを使ったカウンタモデルの完成である(整数カウンタビューのソースをリスト[MVC版整数カウンタビュー]に示す)。ValueModelViewモデルを変える(カレンダカウンタ)次に、整数カウンタからカレンダカウンタに変更してみる。CalendarModelView.javaに変えてから修正する。CalendarModelView.javaのモデルの型をIntegerCountからCalendarCountに変更する点である。これだけだと思って実行したら、初期値が0になっていた。JTextField(model_.getValue().toString);に修正して完成である。中略applet.start();それにinitを呼び出し、最後にアプレットにstartを呼び出すように修正した。CalendarModelViewしかし、アプレットバージョンの実行は、どうもかうまく動かない!!アプレットビューワーでは動いているが、HTMLConverterを使ってもダメである。原因は、今のところわからないが、気にせず先に進む。http://www.st.rim.or.jp/~h_take/MVC/CalendarModelDemo.htmlで起動する。一つモデルを複数のビューから参照する例として1個のカウンタ値を表示するフレームを2個のカウンタが共有する例を挙げる(図[複数ビューカウンタ]参照)。ビューの修正は、値を表示するテキストボックスを取り除く。モデルはボタンアクションを処理するために保持する。カウンタビューにはカウンタモデルを表示する必要がないので、モデルの型はアブストラクト・クラスCountとして定義する。MultiCountView次にメイン関数だが、1個のカウンタオブジェクトを2個のMultiCountViewに渡して2個のカウンタビューを生成する。カウンタモデルのアクションリスナーを生成し、ここでテキストボックスに値をセットする。後は、2個のフレームにそれぞれのビューを詰めて表示するだけである。プログラムをさぼってクローズ処理をテキストボックスにのみ付けた。staticで定義しないとコンパイルできなかった。内蔵したクラスが用意されなかったのでないかと思われる。