2004-12-14
目次
SqueakではじめるSmalltalk入門 第22回 鷲見正人
本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。今回から、先に作成したBankAccountのサブクラスとしてStockAccountを定義します。
預金を扱うBankAccountに対し、今回作成するStockAccountは、株式口座オブジェクトを定義するクラスとして位置づけることにします。簡単のため、扱う株は一種類、つまり株単価は(可変ですが)一意的とします。a StockAccountの振る舞いはa BankAccountとまったく同じです。つまり、メッセージ「deposit: …」で入金し、「withdraw: …」で払い戻しできます。違うのは、内部状態として、株単価と株数を、それぞれインスタンス変数pricePerShare、numSharesに束縛していること、そして出納の際、その金額に見合った株が売買される(保有株数が減ったり増えたりする)ことです。では早速はじめましょう。
まず、思い出していただきたいのは、BankAccountの定義とその手順です。クラスカテゴリペインに新しいクラスカテゴリ「Category-MosaDen」を追加し、その時コードペインに表示されるテンプレートを次のように書き換えることでBankAccountを定義しました。
Object subclass: #BankAccount
instanceVariableNames: 'balance'
classVariableNames: ''
poolDictionaries: ''
category: 'Category-MosaDen'
これは、Objectというクラスに対する「subclass: #BankAccount instanceVariableNames: ‘balance’ classVariableNames: ” poolDictionaries: ” category: ‘Category-MosaDen’」という長いメッセージ送信式になっている話もしました。改めてこのメッセージを読み下すと、Objectというクラスに対して、そのサブクラスとして#BankAccountという名前をこれこれの仕様で作りなさい…ということになります【註】。StockAccountは、BankAccountのサブクラスなのでこの応用で大丈夫ですね。大まかには、レシーバをObjectからBankAccountに、作成するサブクラス名を#StockAccountに、各種仕様を相応しいものに差し替えればよいわけです。
まず、クラスペインで選択されているBankAccountをクリックして選択を解除します。もし、ブラウザを閉じてしまっていたら、BankAccountをbrowse it(cmd-B)して開いてから、改めて選択を解除してください。すると、コードペインにはクラスカテゴリを新設したときと同様にクラス定義用のテンプレートが現われます。これを、先の方針にしたがって書き換え、accept (cmd-S)します。
BankAccount subclass: #StockAccount
instanceVariableNames: 'pricePerShare numShares'
classVariableNames: ''
poolDictionaries: ''
category: 'Category-MosaDen'
acceptと同時に、クラスペインにStockAccountが追加されます。
[fig.A]StockAccountがクラスペインに追加されたところ
http://squab.no-ip.com:8080/mosaren/uploads/22a.png
手始めに、お馴染みのアクセッサを定義しましょう。インスタンス変数pricePerShare、numSharesと同名のゲッター(値を取得するためのメソッド)と、引数をひとつ取る、つまりコロンを付したセッター(値設定用のメソッド)を定義します。まず、メソッドカテゴリを新設します。
メソッドカテゴリペインの黄ボタンメニューから「new category…」を選び、続けて現われるポップアップから「accessing」を選びます。このポップアップには、Object以外のスーパークラスで使用済みのメソッドカテゴリが一覧でき、新規カテゴリ名として採択できるようになっているので、スーパークラスのメソッドカテゴリと同様のカテゴリを追加するときは、new…でいちいち再入力せずに済ませることができます。
accessingカテゴリ追加と同時に、下のコードペインにはメソッドテンプレートが選択状態になって現われるので、マウスカーソルをコードペインに移動してアクティベート(選択状態を確認)した後、次のコードをこのメールからコピー&ペーストします。念のため忘れずにシフト黄ボタンメニューからpretty printしてから、accept (cmd-S)して登録します。
pricePerShare: aNumber
pricePerShare _ aNumber
次は#numShares:ですが、ここで、ちょっとした編集テクニックを紹介します。#numShares:の定義は次のようなものです。
numShares: aNumber
numShares _ aNumber
これは先の#pricePerShare:において「pricePerShare」を「numShares」に全置換したものと同一です。そこで、先頭の「pricePerShare」を選択して「numShares」と入力して置き換えた後、続けて、cmd-Jとタイプしてみてください。これはagainと呼ばれるSmalltalkシステムに特徴的なテキスト編集機能で、直前の置き換え操作を次の出現箇所に適用します。大変便利な機能なのですが、残念ながらMacに継承されることはありませんでした。なお、置き換えたい箇所が残り2カ所以上ある場合は、shift+cmd-Jとタイプします。
ゲッターは、BankAccount >> #balance同様、遅延初期化にしておきましょう。numSharesは未定義時、balance同様に0でよいのですが、pricePerShareは0というわけにはいかないので、適当に30くらいにしておきます。
pricePerShare
^ pricePerShare ifNil: [pricePerShare _ 30]
numShares
^ numShares ifNil: [numShares _ 0]
次回は、#balance:、#balanceを再定義してStockAccountを完成させます。
註:記憶力の良いかたは、スーパークラスがサブクラスを作成することに対して、さっそく疑問を持たれたことと思います。なぜなら、クラスはメタクラスのインスタンスであり、サブクラスを作るにしても、そのメタクラスにnewを送信するのがスジで、継承関係こそあれ、赤の他人のスーパークラスがサブクラスを生じさせるのは相応しくない…と考えることができるからです。
結論から申し上げるとその考え方で合っています。ただ、サブクラス定義時点では、サブクラスのメタクラスは存在せず、まずこれから作る必要があります。メタクラスのクラスはMetaclassなので、これのインスタンスとしてStockAccount classがまず生じ、そこから改めてStockAccountが作成されます。スーパークラスへのメッセージ送信はこうした一連の作業のきっかけ作りに過ぎないのです。
バックナンバー:
http://squab.no-ip.com:8080/mosaren/
小池邦人の「Carbon API 徒然草」
〜 データブラウザ・コールバックルーチン 〜
今回は、SetDataBrowserCallbacks()で登録しておいた2種類のコールバックルーチン、mySetGetItemData()とmyNotification()について詳しく解説したいと思います。
コールバックルーチンの中身の説明をする前に、先んじて準備しておいたデータをどのようにデータブラウザへ新規登録するのかを見てみます。以下が、Object構造体に保存されている画像ファイル情報を一括してデータブラウザに登録するaddItemDataBrowser()ルーチンです(実はデータ本体は登録しないのだが…)。ちなみに、今回のアプリケーションでは登録できる画像ファイル数は1000個までに制限されています。
#define BROW_ID 100 // DataBrowserのControl ID
#define MAX_FILE 1000 // 登録できる画像ファイルの最大数
short addItemDataBrowser( WindowRef window )
{
DataBrowserItemID item[MAX_FILE];
ObjectPtr list[MAX_FILE];
unsigned long ct1,ct,i;
ControlRef browser;
short ret=1;
collectObject( window,list,&ct ); // 登録画像ファイルのObject構造体を集める
setWObjList( window,ct,list ); //カタログウィンドウのWInfo構造体に保存する
for( i=0;i<ct;i++ ) // DataBrowserItemIDにアイテム番号(参照番号)を代入する
item[i]=i+1;
getMyControlRef( window,BROW_ID,&browser ); // DataBrowserのControlRefを得る
GetDataBrowserItemCount( browser,kDataBrowserNoItem,0,
kDataBrowserItemAnyState,&ct1 );
// 現在DataBrowserに登録されているアイテムの個数を得る
RemoveDataBrowserItems( browser,kDataBrowserNoItem,ct1,item,
kDataBrowserItemNoProperty );
// 現在DataBrowserに登録されているアイテムを削除する
if( ct ) // DataBrowserに登録すべき画像ファイルがある場合のみ...
{
AddDataBrowserItems( browser,kDataBrowserNoItem,ct,item,
kDataBrowserItemNoProperty );
// DataBrowserにファイル個数分のアイテム番号をセットする
SetDataBrowserSelectedItems(browser,1,&item[ct-1],kDataBrowserItemsAdd);
// 登録された一番最後のアイテムを選択(セレクション)する
ret=0;
}
DrawOneControl( browser ); // DataBrowser自身を再描画する
return( ret );
}
まずは、collectObject()により、いくつの画像ファイルを登録するのか?そのObject構造体の個数を得ます。Object構造体にはファイル情報(ファイルタイプとFSSpec構造体)が含まれており、それらはすでにドキュメントの読み込み、ファイルやフォルダのドラッグ&ドロップなどにより作成されています。
最初に、配列番号に1を足した値をアイテム番号(参照番号)としてitem[MAX_FILE]に代入しておきます。続いてGetDataBrowserItemCount()とRemoveDataBrowserItems()により、現状のリスト内容をすべて削除し新規登録に備えます。AddDataBrowserItems()により画像ファイル個数分(ct)のアイテム番号をデータブラウザに追加登録したら、SetDataBrowserSelectedItems()で一番最後のアイテムを選択(セレクション)し作業は終了します。
AddDataBrowserItems()の引き数を見てみると、アイテム番号の方は渡されていますが、Object構造体自体は渡されてはいません。では、それはどの時点で渡されて表示に使われるのでしょうか? 実は、実際にデータを表示する場合には、先んじて登録したアイテム番号をうまく利用することになります。その仕事を担当しているのが、ひとつめのコールバックルーチンmySetGetItemData()です。
pascal OSStatus mySetGetItemData( ControlRef browser,DataBrowserItemID itemID,
DataBrowserPropertyID property,DataBrowserItemDataRef itemData,
Boolean changeValue )
{
short ret=0;
CFStringRef cref;
IconRef iref;
ObjectPtr optr;
short lav;
Str255 str;
if( ! changeValue )
{
getWObject( GetControlOwner( browser ),itemID-1,&optr );
// itemID(アイテム番号)に対応するObject構造体(ファイル情報)を得る
switch( property )
{
case 'name': // 表示対象カラムがファイル名の場合
GetIconRefFromFile( &optr->o_fsc,&iref,&lav );
// 画像ファイルのアイコンのIconRefを得る
SetDataBrowserItemDataIcon( itemData,iref );
// DataBrowserの'name'カラムの先頭にアイコンを表示
if( cref=CFStringCreateWithPascalString( NULL,optr->o_fsc.name,
CFStringGetSystemEncoding() ) )
// ファイル名をStr255からCFStringRefに変換
{
SetDataBrowserItemDataText( itemData,cref );
// DataBrowserの'name'カラムにファイル名を表示
CFRelease( cref );
}
break;
case 'type': // 表示対象カラムがファイルタイプの場合
*str=4;
BlockMoveData( &optr->o_type,str+1,4L );
if( cref=CFStringCreateWithPascalString( NULL,str,
CFStringGetSystemEncoding() ) )
// ファイルタイプををStr255からCFStringRefに変換
{
SetDataBrowserItemDataText( itemData,cref );
// DataBrowserにのtypeカラムにファイルタイプを表示
CFRelease( cref );
}
break;
default:
ret=errDataBrowserPropertyNotSupported;
break;
}
}
return( ret );
}
このコールバックルーチンには、リスト表示させるべきデータをデータブラウザに「渡す処理」(セット)と、表示されているデータを「得る処理」(ゲット)を実装します。引き数のchangeValueがfalseならば、データを渡すことを意味し、逆にtrueであれば、データを得ることを意味します。今回は渡す処理のみが実装されています。例えば、スクロールでリストの表示領域が変更された場合には、このコールバックルーチンが呼び出され、表示すべきデータの再セットを促すわけです。ファイル情報のうち、どのデータをセットすべきかは、引き数のカラムタイプ(property)とアイテム番号(itemID)から判断します。表示すべきファイル名とファイルタイプはObject構造体に保存されており、それはアイテム番号により1対1で参照することが可能です。アイテム番号からObject構造体を得るのには、自作のgetWObject()ルーチンを利用します。
次に、もう片方のコールバックルーチンmyNotification()を紹介します。こちらのコールバックルーチンは、ユーザがデータブラウザに対して何らかの操作を実行した時に呼ばれます。つまり、データブラウザのデータ内容や表示状態に変化させるイベントが発生した場合に、その種類をDataBrowserItemNotification(message)に代入して教えてくれるわけです。
pascal void myNotification( ControlRef browser,DataBrowserItemID itemID,
DataBrowserItemNotification message )
{
WindowRef wptr,wptr1;
ObjectPtr optr;
wptr=GetControlOwner( browser ); // カタログウィンドウのWindowRefを得る
getWObject( wptr,itemID-1,&optr ); // アイテム番号から対象Object構造体を得る
switch( message )
{
case kDataBrowserSelectionSetChanged: // 選択アイテムが切り替わった
mainteCatalogWindow( wptr ); // ボタン表示や画像表示をメンテナンス
break;
case kDataBrowserItemDoubleClicked: // アイテムがダブルクリックされた
if( getModifiers()&optionKey ) // オプションキーが押されている場合
launchApplication( &optr->o_fsc ); // 画像に対応するアプリを起動
else
openViwerWindow( wptr,&optr->o_fsc,&wptr1 ); // 画像をオープン
break;
}
}
例えば、アイテムのダブルクリックでその画像ファイルをオープンさせたい時に、この仕組みを利用しています。本アプリケーションでは、アイテムのセレクション(選択範囲)の変更(kDataBrowserSelectionSetChanged)と、マウスによるアイテムのダブルクリック(kDataBrowserItemDoubleClicked)の2種類のイベントに対応しています。こうしたイベントに対するメッセージとしては、アイテムの追加(kDataBrowserItemAdded)や、アイテムの削除(kDataBrowserItemRemoved)など、いくつもの種類が用意されていますので、それぞれの詳しい内容についてはControlDefinitions.hを参照してください。
次回は、メインウィンドウを閉じるときに呼ぶcloseCatalogWindow()ルーチンを調べてみます。内部で使われている自作ルーチンだけではなく、データブラウザに登録されているファイル一覧の保存と読み込みを担当しているルーチンについても解説する予定です。
つづく
「Behind the WebObjects」 第33回 田畑 英和
前回はカスタムEOの実装方法を取り上げ、データの初期化について解説しました。今回はデータの正当性の検証方法について説明したいと思います。
アプリケーション上では様々なデータを扱うことになりますが、個々のデータが適切な値であることを検証しなければならないことが多々あります。例えば、年齢のデータを扱う場合は0以上になるでしょうし、メールアドレスには”@”が含まれる必要がありますし、日本の郵便番号は3+4の7桁の数値になります。もしデータの正当性をチェックしなければ、不正なデータが混入する可能性があり、そのデータを利用した処理に影響が出るおそれがあります。
不正なデータが混入することを防ぐには、入力されたデータの正当性を検証する必要があります。正当性を検証する方法はいくつか考えられますが、例えばJavaScriptを用いてWebブラウザ上で入力データをチェックすることが考えられます。この方法ですとクライアント上で正当性の検証がおこなえるため、サーバとやりとりすることなくデータをチェックすることができます。
またモデルファイル上でもNullのチェック、文字数(byte数)の上限チェック、リレーションのチェックなどを設定することができます。モデル上での制約にひっかかった場合はExceptionが発生しデータベースに不正なデータが保存されるのを防止できます。これらのチェックはEOModeler上で簡単に設定することができます。
このようにクライアント側での入力データチェックや、モデルファイルを用いた入力データのチェックがおこなえますが、カスタムEOに正当性検証用のロジックを埋め込んでおくこともできます。カスタムEOへのロジック追加になりますのでJavaプログラミングをおこなうことになります。
データを初期化するメソッドとして前回awakeFromInsertion()を紹介しましたが、データの正当性検証用のメソッドもあらかじめ用意されています。このメソッドは、データを保存するときに(EOEditingContextのsaveChanges()を実行したとき)に自動的に呼び出されます。
・データの正当性検証用メソッド
public void validateForSave()
throws NSValidation.ValidationException
独自の正当性検証ロジックを組み込むには、次のようにカスタムEOクラスでこのメソッドをオーバーライドします。このときまずはsuper()を用いて親クラスのvalidateForSave()を呼び出してから、独自の検証処理を実行するようにします。
もし検証結果が正しくない場合は、NSValidation.ValidationExceptionの例外を発生させるように実装します。
public void validateForSave()
throws NSValidation.ValidationException {
super.validateForSave();
// 検証処理
if(checkError() == false) {
throw new NSValidation.ValidationException("Error");
}
}
private boolean checkError() {
// 検証ロジック
}
このようなコードを実装しておけば、saveChanges()の実行時に自動的に呼び出され、任意の検証処理をEOに対して適用することができます。さらに必要に応じてこのメソッドを直接実行すれば、任意のタイミングで検証処理を明示的に実行することもできます。
validateForSave()はEOレベルでの正当性を検証するメソッドですが、各プロパティ(アトリビュート)レベルでの検証用ロジックを実装することもできます。各アトリビュートごとに以下のメソッドを実装します。
このとき”validateKey”の”Key”の部分を、実際に検証をおこないたいアトリビュート名に書き換えたメソッドを実装します。アトリビュート名の頭文字は大文字にしてください。
・プロパティレベルの正当性検証用メソッド
public Object validateKey(Object aValue) throws
throws NSValidation.ValidationException
例えばrevenueアトリビュートを検証するメソッドは以下のように実装することができます。プロパティレベルの検証メソッドは対応するアトリビュートの値が変更される前に呼び出されます。さらにvalidateForSave()を実行したときにも、super.validateForSave()からプロパティレベルの検証メソッドが呼び出されます。
public BigDecimal validateRevenue(Object aValue)
throws NSValidation.ValidationException {
BigDecimal revenue;
if (aValue instanceof BigDecimal) {
revenue = (BigDecimal)aValue;
} else {
throw new NSValidation.ValidationException("Error1");
}
if(revenue.intValue() < 0) {
throw new NSValidation.ValidationException("Error2");
}
return revenue;
}
今回紹介したメソッドを用いれば、不正なデータの入力を防ぐことができ、正しいデータのみがデータベースに保存できるようになります。ただしWebアプリケーションの場合、サーバ側ですべての正当性の検証をおこなうとなるとクライアントとのトランザクションが増えてしまいますので、JavaScriptを用いたクライアント側での検証処理と組み合わせるなどの工夫をおこなえば、効果的な検証処理を実現することができます
ニュース・解説
今週の解説担当:新居 雅行
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃Mac OS Xでのデバッグ手法を1つにまとめた文書が公開
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Technical Noteとして、Mac OS Xのさまざまなデバッグ手法について解説する文書が公開されている。Crash ReporterやBSDレイヤで利用できる機能、さらにはgdbを使った方法など、デバッグについての情報がひとつにまとまった文書となっている。CarbonやCocoaでは内部動作をトレースするような機能の説明がある。Macでプログラミングをしている人は、かならずどこかに興味ある情報が見つかるだろう。
Technical Note TN2124: Mac OS X Debugging Magic
http://developer.apple.com/technotes/tn2004/tn2124.html
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃サイン付きアプレットとLiveConnect
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Java 1.4.2 Update 2では、LiveConnectを使ってJavaScriptからJavaアプレットを呼び出したとき、そのアプレットがサイン付きで認証される前に呼び出しがあった場合にハングアップしてしまう。これを回避するには、LiveConnect利用をする前に、あらかじめサイン付きアプレットの認証を終わらせておくようにすればよい。サンプルプログラムも掲載されている。
Technical Q&A 1395: Hang launching signed Applets from JavaScript
http://developer.apple.com/qa/qa2004/qa1395.html
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃マウスポインタ変化のための矩形領域を設定する
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
マウスポインタを位置によって形状を変えるために、矩形領域をaddTrackingRectメソッドで登録したけど何もおこらないという問題に対する回答が掲載されている。これは、ビューをウインドウに登録する前にマウス応答のための矩形領域を追加するために発生すると解説されている。
addTrackingRectメソッドを、viewDidMoveToWindowあるいはawakefromNibメソッドで利用するなど、適切な順序で利用するようにすればよい。
Technical Q&A: Why aren't my tracking rects working?
http://developer.apple.com/qa/qa2004/qa1355.html
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃OracleのJDeveloperをMac OS Xで使う
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ADCのサイトで、Oracleがフリーで配布している開発ツールのJDeveloperをMac OS Xで利用する方法が記事で紹介されている。Oracle Databaseの利用が前提となるが、現在もOracle Database 10g Early Adopters Release 2が配布されているので、それを使うことが前提となる。
Using Oracle JDeveloper on Mac OS X
http://developer.apple.com/tools/jdeveloper.html
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃FileMakerのデータベースとSQLを同期するツール
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Garrison Computer Servicesは、FileMakerのデータベースとSQLデータベースの内容を同期させるツール「fmSQL Synch v1.0」をリリースした。少しの修正で同期ができるとしている。また、FileMakerから他のデータベースへの移行あるいは逆への移行にも使えるツールである。ただし、FileMaker Pro 4以降対応ながら、FileMaker Pro 7には対応していない。次のリリースでVer.7への対応を行う予定としている。価格はシングルユーザ版で$129などとなっている。なお、同社では、さまざまな稼働環境での同期ツールを今後もリリースする予定である。
fmSQL Synch v1.0
http://www.garrison.com.au/products/fmsql_synch.html
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃VNCを用いたテスティングツールがアップデート
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Redstone Softwareは、ネットワーク越しに他のコンピュータをコントロールすることで、GUIの自動テストを行うツール「Eggplant 2.0」をリリースした。テスト手続きはSenseTalkという独自の言語を使って記述するがシンプルで学習しやすいものとしている。そして、VNCのプロトコルを用いて他のコンピュータをコントロールするため、Macだけでなく、Windowsについてもテスティングが可能となっている。バージョンアップにより、テスト機能の強化とともに、同社が配布しているOSXvncを使えば、テスト環境の存在をRendezvousを通じて参照できるようになる。
Eggplant
http://www.redstonesoftware.com/whatiseggplant.html
MOSAからのお知らせと編集後記は割愛します