MOSA Developer News[MOSADeN=モサ伝]第142号
2004-12-28
目次
- SqueakではじめるSmalltalk入門 第24回 鷲見 正人
- 小池邦人の「Carbon API 徒然草」
- 「Behind the WebObjects」 第34回 田畑 英和
SqueakではじめるSmalltalk入門 第24回 鷲見 正人
本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。今回は、「再定義していないメソッドが、なぜ多態性を発揮できるのか」についてです。
簡単のため、2つある出納用メソッドのうち、#deposit:のみに注目します。次のようなスクリプトをdo it (cmd-D)した(あるいは、stockの中身を観察するためにinspect it (cmd-I)した)と想定します。
| stock |
stock _ StockAccount new.
stock deposit: 300.
^ stock
ここで、stockに束縛されたa StockAccountは、「deposit: 300」というメッセージを受け、自らが属するクラスStockAccountで、#deposit:というメソッドを探します。しかし、我々はそれを再定義していないので、該当するメソッドを見つけることは叶いません。しかたがないのでスーパークラスであるBankAccountにおいて改めて#deposit:を探し、見つけてそれを起動します。【註】
BankAccount >> deposit: aNumber
self balance: self balance + aNumber
すでにご存じのとおり、もともとこのメソッドは、selfにa BankAccountが束縛されることを念頭に記述されたものです。その振る舞いは、self balanceの返値とaNumberの和をパラメータにして、「balance: …」というメッセージをselfに送る…というものです。selfがa BankAccountなら、「balance」、「balance: …」というメッセージはそれぞれ対応するBankAccount >>#balance、BankAccount >> #balance:を起動して話はおしまいです。
BankAccount >> balance
^ balance ifNil: [balance _ 0]
BankAccount >> balance: aNumber
balance _ aNumber
しかし、今selfには、このメソッドを起動するきっかけを作ったメッセージ式におけるレシーバ、つまり、a StockAccountが束縛されています。したがって、このコンテキスト(文脈、あるいは、メソッド実行時のインタープリタの内部状態)において、「balance」および「balance: …」というメッセージを受けたself、つまりa StockAccountは、自らの属するクラスStockAccountにて#balance、#balance:メソッドを探し、見つけることができれば(当然、我々はそうしたメソッドをStockAccountに再定義済みなので必ず見つかるわけですが)それらを起動します。
StockAccount >> balance
^ self pricePerShare * self numShares
StockAccount >> balance: aNumber
self numShares: aNumber asFloat / self pricePerShare
この2つのメソッドは、インスタンス変数balanceへのアクセスを行なうBankAccount >> #balance、#balance:と、同名で、同種のメッセージで起動される点では一緒なのですが、その内容、つまり振る舞いはまるで異なります。したがって結果的に、これらのメソッドを起動するBankAccount >> #deposit:や#withdraw:も、その振る舞いを変える、つまりレシーバに合わせて相応しい多態性を発揮できる、というわけです。少々込み入った話になってしまいましたが、以上が、再定義なしに出納用メソッドがうまく機能する“からくり”です。
もちろん、スーパークラスのメソッドに、このような振る舞いをさせるには、設計段階からの準備が必要です。たとえば、当初、出納用メソッドを定義する際に候補にあった、インスタンス変数balanceへの直接参照で記述してしまっていたならば、単純にそれらをサブクラスで再定義するしかStockAccountを実現する方法はなくなります。
BankAccount >> deposit: aNumber
balance _ balance + aNumber
BankAccount >> withdraw: aNumber
balance _ balance - aNumber max: 0
↓
StockAccount >> deposit: aNumber
numShares _ numShares + (aNumber asFloat / pricePerShare)
StockAccount >> withdraw: aNumber
numShares _ numShares - (aNumber asFloat / pricePerShare) max: 0
すでに扱った初期化の問題を無視すれば、確かにこれでうまくStockAccountは機能します。もっともこの場合、結果として、スーパークラスBankAccountのメソッドをすべてオーバーライドした状態になるので、ここで継承を使う“うま味”はまったくなくなってしまうのですが…。
註:メソッドのメッセージパターンには、本来不要な「クラス名 >> 」という表現を追記して、そのメソッドがどのクラスに帰属するのかを明示的にすることがあります。もしブラウザなどにコピー&ペーストしてそのまま使用するときは、「クラス名 >> 」の部分は選択せずに、それを省いた残りをコピーするようにしてください。
バックナンバー:
http://squab.no-ip.com:8080/mosaren/
小池邦人の「Carbon API 徒然草」(2004/12/24)
Navigation Service APIの活用(その1)
今回は、メインウィンドウを閉じるときに実行されるcloseCatalogWindow()の内部で使われている自作ルーチンを調べてみます。加えて、ファイルの読み込み時や保存時に活用するNavigation Serviceについても解説いたします。まずは、ウィンドウを閉じる処理を担当しているcloseCatalogWindow()をもう一度見てみましょう。
short closeCatalogWindow( WindowRef window,short quit )
{
short chk,ret=0;
if( IsWindowModified( window ) ) // ドキュメントに何らかの変更があったか?
{
chk=navMySaveAlert( window,quit ); // 保存を行うか尋ねるアラートを表示
if( chk==1 )
ret=saveCatalogFile( window ); // 「保存」ボタン(ドキュメント保存)
else if( chk==2 ) // 「キャンセル」ボタン(処理中止)
ret=1;
} // 「保存しない」ボタンが押された時
if( ret==0 ) // もしくは正常に保処理が終了した時
disposeCatalogWindow( window ); // ウィンドウを閉じてメモリの解放
return( ret );
}
このルーチンは、ユーザがファイルメニューから「閉じる」を選択するか、ウィンドウタイトル左端のクローズボックスをクリックした時に実行されます。また、アプリケーションを終了させる時点でオープンしているウィンドウがあれば、終了の処理を担当しているquitApplication()から呼ばれます(引数quitに1が代入される)。ドキュメントに何らかの編集が施されているかどうかをWindow Manager APIのIsWindowModified()で調べ、もしそうであれば、適切な処理を追加実行しています。その最初の仕事が「まだ保存されていませんがどうしますか?」という内容のアラートを表示することです。このアラート表示を行っているのが、以下のnavMySaveAlert()ルーチンです。
short navMySaveAlert( WindowRef window,short quit )
{
NavAskSaveChangesResult reply; // アラートのボタン番号が返る
short ret=0;
NavDialogOptions opt; // オプション用構造体
NavAskSaveChangesAction act; // タイトルの種類を選ぶ
SysBeep( 1 ); // ビープ音を一度鳴らす
myCursor( 0 ); // カーソルの形状を矢印に
NavGetDefaultDialogOptions( &opt ); // アラートのオプションを初期化
GetWTitle( window,opt.savedFileName ); // ドキュメント名を設定する
GetIndString( opt.clientName,128,1 ); // アプリケーション名を設定する
if( quit ) // アプリ終了時に呼ばれた...
act=kNavSaveChangesQuittingApplication;
else // ウィンドウを閉じる時に呼ばれた...
act=kNavSaveChangesClosingDocument;
if( ! NavAskSaveChanges( &opt,act,&reply,NULL,NULL ) ) // アラート表示実行
ret=reply;
return( ret ); // ボタン番号を返す(1.保存 2.キャンセル 3.保存しない)
}
Mac OS XのCarbon Frameworkには、ファイルの「読み込み」や「保存」の実現を容易にするために、「Navigation Service」というAPI群が用意されています。例えば、あるアプリケーションでファイルをオープンしようとすると、ファイル一覧を表示したダイアログが表示されますが、それを実行しているのがNavigation ServiceのAPIなのです。そうしたAPIのうち、navMySaveAlert()でファイル保存を促すためのアラートを表示しているのは、NavAskSaveChanges() APIです。NavAskSaveChanges()と同種のアラート用APIとしては、NavCustomAskSaveChanges()やNavAskDiscardChanges()などがあります。後者はファイルメニューの「復帰…」が選択された時に、「変更内容を破棄する」というタイトルのアラートを表示します。このAPIを使えば、破棄を許すかどうかをOKかキャンセルボタンでユーザに選択させることができるわけです。
Navigation Serviceで表示できるダイアログやアラートは、NavDialogOptions構造体の内容を操作することにより、色々なオプション機能を追加することが可能です。ここでは、まずNavGetDefaultDialogOptions()を実行し、NavDialogOptions構造体の内容を初期化しています。その後、ドキュメント名をopt.savedFileNameに代入することで、「書類”…”に加えられた変更を保存しますか?」と表示される注意文章の”…”の部分に、ドキュメント名を挿入することが可能です。また、アラートのタイトルは2種類から選択でき、引数で渡すNavAskSaveChangesActionをkNavSaveChangesClosingDocumentにすれば、タイトルは「閉じる前に変更内容を保存する」となり、kNavSaveChangesQuittingApplicationにすれば「終了する前に変更内容を保存する」となります(日本語の場合)。
NavAskSaveChanges()は、ユーザがクリックしたボタンの番号をNavAskSaveChangesResultに返してきます。その値は以下のように定義されています。
typedef UInt32 NavAskSaveChangesResult;
enum {
kNavAskSaveChangesSave =1, // 「保存」ボタン
kNavAskSaveChangesCancel =2, // 「キャンセル」ボタン
kNavAskSaveChangesDontSave =3 // 「保存しない」ボタン
};
closeCatalogWindow()では、このボタン番号を参照することで、「保存」「キャンセル」「保存しない」に対応した処理に分岐させます。そのうち「保存」ボタンが押された場合には、以下のsaveCatalogFile()ルーチンが呼ばれています。この中でファイル保存用ダイアログの表示を担当しているのがnavMyPutFile()ルーチンですが、このルーチンの中でも、NavPutFile()というNavigation Service APIが利用されます。このnavMyPutFile()やNavPutFile()については、次回に詳しく解説したいと思います。
short saveCatalogFile( WindowRef window )
{
Str255 title;
short ret=1;
FSSpec fsc;
if( ! getWFSSpec( window,&fsc ) ) // すでにファイルへ保存されているか?
ret=saveObjectFile( window,&fsc ); // その場合にはFSSpecを得て上書き保存
else
{ // まだファイルに保存されていない...
GetWTitle( window,title ); // 仮のドキュメント名を得る
if( ! navMyPutFile( title,&fsc ) ) // ファイル保存ダイアログを表示
{
if( ! saveObjectFile( window,&fsc ) ) // 得たFSSpecを使い新規保存
{
SetWTitle( window,fsc.name ); // ドキュメント名を再設定
setWFSSpec( window,&fsc ); // FSSpecをWcore構造体に保存
ret=0;
}
}
}
if( ret==0 )
SetWindowModified( window,0 ); // 編集済みドキュメントとする
return( ret );
}
NavDialogOptions構造体の詳しい内容や、その中で定義されているNavDialogOptionFlagsの種類と機能については、Universal InterfacesのNavigation.hを参照してみてください。次回は、Navigation Serviceの話の続きとなります。navMyPutFile()から呼ばれているNavPutFile()を含め、いくつかのNavigation Service APIの使用方法を解説したいと思います。
つづく
「Behind the WebObjects」 第34回 田畑 英和
さて今年最後の連載になりますが、今回はデバッグ出力について取り上げてみたいと思います。プログラムが意図したとおりに動作しない場合はデバッグ用の出力をおこない、正しいデータがやりとりされているかを確認することが重要になります。
Formデータの出力
まずはHTML Formの出力についてみてみます。クライアントから送信されてきたFormデータを出力する場合は以下のようなコードを追加します。特定のコンポーネントに対するデータのみを出力する場合は、該当するコンポーネントに、すべてのFormデータを出力する場合はApplication.javaにこのメソッドを追加してください。このときsuperを呼び出すのを忘れないようにしてください。
・Formデータの出力
public void takeValuesFromRequest(WORequest aRequest,
WOContext aContext) {
System.out.println(aRequest);
super.takeValuesFromRequest(aRequest, aContext);
}
これでクライアントから送信されてきたFormデータを出力できますが、Form以外のデータ(HTTPのヘッダー情報など)も一緒に出力されてしまいますので、Formデータのみを出力できるようにコードを書き換えてみます。
・Formデータの出力(改)
public void takeValuesFromRequest(WORequest aRequest,
WOContext aContext) {
NSArray allKeys = aRequest.formValueKeys();
for(int i = 0 ; i < allKeys.count() ; i++) {
String aKey = (String)allKeys.objectAtIndex(i);
System.out.println(aKey + " : "
+ aRequest.formValueForKey(aKey));
}
super.takeValuesFromRequest(aRequest, aContext);
}
・Formデータの出力結果
3.3 : Match
3.1 : The
FormデータのみをKeyとValueのペアとして出力できるようになりましたが、キーの値が"3.3", "3.1"といった機械的な値になっているためデバッグ情報としては少し分かりにくいです。これはWebObjectsが動的にHTMLを生成するときに、Formエレメントの"name"属性を自動的に生成するためです。
WebObjects Builderで各Formエレメントの"name"属性を明示的に設定しておけば、次のように分かりやすくFormデータを出力することができます。
・Formデータの出力結果(改)
title : The
submit : Match
ヘッダー情報の出力
次にヘッダー情報の出力をおこなってみましょう。takeValuesFromRequestはFormデータの送信がおこなわれたときにしか呼び出されませんので、今回はリクエスト時に必ず呼び出されるawakeを利用してみたいと思います。
public void awake() {
super.awake();
NSArray allKeys = context().request().headerKeys();
for(int i = 0 ; i < allKeys.count() ; i++) {
String aKey = (String)allKeys.objectAtIndex(i);
System.out.println(aKey + " : "
+ context().request().headerForKey(aKey));
}
}
これでブラウザの"user-agent"などの情報を取得することができます。今回はデバッグ用の出力ということで解説をしていますが、これらのヘッダー情報を実際の処理に利用することも可能です。たとえば"user-agent"によって処理を切り替えるような応用が考えられます。
リクエストーレスポンスデータのダンプ
今回紹介したコードを利用すれば任意のタイミングで、必要な情報を出力することができますが、すべてのリクエストとレスポンスデータをヘッダーも含めてファイルに書き出すことができます。
起動引数の"WORecordingPath"で任意のパスを指定すれば、そのパスにリクエストとレスポンスごとのテキストファイルが作成されます。このときファイル名には自動的に連番が付けられます。
・起動引数の指定
-WORecordingPath /tmp/WOLog
・ダンプされたファイル
Host:/tmp/WOLog.rec tabata$ ls
0000-request 0001-request 0002-request
0000-response 0001-response 0002-response
SQLの出力
WebObjectsではEOFを利用してデータベースアクセスをおこなうため、通常はSQLを直接扱いませんが、実際にはEOFの内部で必要に応じてSQLが自動生成されています。
WebObjectsのロギングの機能にはSQLをログ出力する機能がありますので、Application.javaのコンストラクタに以下のコードを追加しておけば、アプリケーション内で生成されるすべてのSQLを出力することができます。
・SQLの出力
NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupSQLGeneration);
NSLog.allowDebugLoggingForGroups(NSLog.DebugGroupDatabaseAccess);
このようにして出力したSQLはパフォーマンスのチューニング時に重要な情報になりますので、EOFを使用する場合でもSQLの知識があったほうが役に立つでしょう。
まとめ
WebObjectsにはデバッグ用の出力に役立つAPIが用意されていますし、JavaにはLog4jのようなロギングのライブラリがありますので、これらを活用すれば効率よくデバッグ作業がおこなえるようになります。
コーディングをおこなってアプリケーションを実行しているだけでは、問題が発生してもなかなか原因が分からないことがありますので、今回紹介した方法をぜひご利用ください。それでは皆様よいお年を!!
ニュース・解説
★今週は開発関連のニュースがありませんでした。
MOSAからのお知らせと編集後記は割愛します
Apple、Mac OSは米国アップルコンピュータ社の登録商標です。またそのほかの各製品名等はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
特定非営利活動法人MOSA http://www.mosa.gr.jp/
Copyright (C)2004-2006 MOSA. All rights reserved.