RealBasic University

このコラムはStone Table Softwareのオーナーであり、またREALbasic Developerの編集者でもあるMarc Zeedar氏により書かれたものを、著者の許可を得て翻訳したものです。この翻訳はHREM Researchにより提供されています。この日本語版へのご意見はRBU-Jまでご連絡下さい。

URL: http://www.applelinks.com/rbu/097/
INDEXに戻る

OOP University:パート 21

先週、我々はオブジェクト指向プログラムのデザインの仕方についての学習を開始しました。プログラムのデザインにおける3つのステップは次のようになります。

  1. 問題を定義する。
  2. 解決策を考える。
  3. 解決策を評価する。

何か実践的なものを考えるため、私は架空のプロジェクトであるウェブログ・プログラム、RBlogを提案しました。必要事項を定義した後、私はこのプロジェクトのためのデータ構造に対する少なくとも三つの完全に異なるアプローチを考えてくるよう宿題を出しました。

RBlogのデータ構造

私が今まで繰り返し述べてきたように、プログラムのデータ構造はプログラムの中心となるものです。プログラムとはそのユーザインターフェースであると考えるかもしれませんが、それはデータ構造上に存在するレイヤーにすぎません。ユーザが行うことはすべて、プログラムがデータを扱えるような方法に変換されなければなりません。

データ構造は、予期されるニーズを容易に実現できるように編成される必要があります。これは、ソート、高速検索、追加、削除、あるいは再定義(拡張またはカスタム化)の組み合わせとなるでしょう。

プログラミングのすべてはトレード・オフであることを覚えておきましょう。例えば、データを検索するには時間がかかります。しかし、そのデータのインデックスがあれば、データをはるかに早く検索することができます(このようにして、Sherlockは数秒の内にハードドライブの全内容を検索します)。しかし、そのインデックスを維持することは、データの追加や削除のような他のプログラム操作を遅くするでしょう(ここでも、Sherlockがそのインデックスを生成していることについて考えて下さい)。あなたが決定する選択はすべて妥協です。あなたの仕事は、プログラムに最適なデータ構造を選択することです。

RBlogの必要事項を念頭に置き、そのためにどのようなデータ構造を思い浮かべることができるでしょうか?

データ構造 #1:文字列配列

明白なものからはじめましょう。つまりは単純な配列です。それは線形ですので、特に素早くは検索はできないでしょう(すべての要素が検索中に検討されなければなりません)。しかし、プログラムするのは簡単です。ウェブログの各入力は、ほんのわずかな入力(日付/時間、タイトル、カテゴリーおよびテキスト)から構成されているので、各入力に対して複雑なオブジェクトを組み込むというより、文字列にすべての入力を保存してしまいましょう。各フィールドはタブで区切ることができ、nthField関数で簡単に分離できます。これは、簡単なinStr()検索でタイトル、サブジェクト、日付やテキストのすべてを一回で検索できるという利点があります。

これは悪くはありませんが、特によい方法でもありませんでした。他のアイデアもいくつか考えてみましょう。

データ構造 #2:辞書(dictionary)

REALbasicは強力なdictionaryオブジェクトがあります。collectionと似た特別なデータ構造ですが、dictionaryには要素をさらに素早く検索するハッシュ・テーブル(インデックス))を持っています。REALbasicがdictionaryのハッシュ・テーブルを管理してくれますので、この構造はとても使いやすく、驚くほど早く検索できます。dictionaryは、valuekeyという二つの部分から成り立っています。keyは要素を一意的に認識するために使用されます(例えば、データベースの中のレコードIDです)。valueは、保存されているデータです。keyvalueのどちらもバリアント型で、あなたが希望するどのようなデータ型(文字列、ナンバー、オブジェクトなど)でも保存できます。

例えばRBlogでは、我々はkeyとして日付のtotalSecondsプロパティを使用することができるでしょう。二つの投稿が、まったく同じ日付と時間であることはあり得ないので、totalSecondsプロパティはそれぞれの投稿で固有のものです。また、dictionaryのためのインデックスとして、日付/時間を使用することによって、日付/時間でどのような投稿も瞬時に検索することができます。

データ構造 #3:データ・オブジェクト・アプローチ

もう一つのアイデアは、より従来型のオブジェクト指向のアプローチであり、我々のデータを保存する一連のデータ構造を作成することです。データ・オブジェクトは、配列としてマスター・オブジェクトに保存することができます。適切にデータをカプセル化することにより、我々はマスター・オブジェクトにデータ操作のインターフェースを含めることができます。したがって外部のコードは実際のデータ構造に関知しません。これによって我々は、後日プログラムを修正することなくデータ構造を変更する柔軟性が得られます。

構造は次のようになります。

お分かりのように、dbClassがデータを操作するメソッドを所有し、外部コードにデータを操作するインターフェースを与えています(実際にはここに示しているよりも、さらにたくさんのルーチンが必要になるでしょう)。entryClass構造は、単一レコード用のデータを保持します。dbClassは、プログラムのすべてのデータを保持するentryClassオブジェクトの配列を含んでいます。

データ構造 #4:多重リストのアイデア

これまでの我々のアイデアはそう悪くありませんが、お見事とも言えません。それらは、我々の要求の一部を満たすだけです。例えば、我々が必要なものには、日付/時間によって要素を素早く検索する方法があります。しかし、RBlogはカテゴリー(トピック)によってウェブ・ページを作成する必要もあるので、我々はトピックによっても瞬時に検索する方法があれば理想的でしょう。複数の方法で整理されたデータ構造を同時に持つならば、それがベストでしょう!

しかし、それはばかばかしいですね。複数の方法でデータを保存することは、冗長で無駄が多いでしょう。しかし、ちょっと待ってください。我々に特有のニーズを考えてみてください。我々はデータベースが有限のサイズ(10年後には、多く見積もって11,000のレコードがあるだろうと予想しています)であると決めました。だからおそらく、そのような小さなデータベースでは少しくらいの冗長さはそれほど問題ではないでしょう。少なくともこの選択肢を検討してみましょう。我々はどのように多重リスト・データベースの導入に取り掛かればよいでしょうか?

はじめに、我々はREALbasicの素早いdictionaryクラスが、特定のレコードに対して高速検索が可能であることを知っています。よって、そのような構造を用いて、検索システムを構築することを試してみましょう。複数の種類のデータに対して、素早い検索を希望するならば、我々はそれぞれのデータ型に対して次のような別々のdictionaryが必要になるでしょう。

二番目に、オプション #3のデーオブジェクト配列に似た構造を使用して、データを整理すると仮定しましょう。それから、高速検索のためのdictionaryオブジェクトに、それらのデータオブジェクトをコピーすることができるでしょう。ちょっと待ってください、どうしてコピーなのですか?オリジナルの要素のインデックス番号を、リンクとして含めるのでは駄目なのでしょうか?そうすればオブジェクトをコピーする必要はありませんし、そのレファレンスを取得して、そこにアクセスできます。それで、我々のdictionaryオブジェクトは複製のオブジェクトの代わりに、少しの数値を保存するだけで十分になります!

これは興味深い方法に見えますが、私は2、3の大きな問題をすでに確認しています。例えば次のように1〜4にインデックスされたデータオブジェクトの配列があるとしましょう。

  
theData(1) = [date = "5/20/2003"
time = "10:45 a.m."
subject = "Movie Review"
title = "A great movie"
text = "'Matrix Reloaded' is really cool."]

theData(2) = [date = "5/21/2003"
time = "11:41 p.m."
subject = "Book Review"
title = "A great book"
text = "'Mrs. Dalloway' is excellent."]
theData(3) = [date = "5/22/2003"
...
theData(4) = [date = "5/23/2003"
...
etc.

findByDatedictionaryオブジェクトは次のように見るかもしれません。

  
findByDate.value("5/20/2003") = 1 // index of entry 1
findByDate.value("5/21/2003") = 2 // index of entry 2
findByDate.value("5/22/2003") = 3 // index of entry 3
findByDate.value("5/23/2003") = 4 // index of entry 4

問題なさそうです。仮に、次のように検索するとすれば、

  
findByDate.hasKey("5/21/2003")

我々は即座にtrue/falseの結果を得るはずです。trueであれば、一致する入力があるので、次のようにして取り出すことができます。

  
theIndex = findByDate.value("5/21/2003")

その時、変数theIndexは、データ配列のインデックスを保持しているでしょう。とてもよい方法のようですが、次の二つの大きな問題があります。

一つ目は、dictionaryのkeyはユニークでなければなりません(そうでなければ、最初のものだけしか検索できません)。我々がkeyとして日付を用いるならば、同日に複数の投稿があるかもしれないので、それはユニークではありません。残念ですが、投稿の具体的な時間を知ることは困難でしょう。例えば、日付によって反復し、「その日に何か投稿があったか?」と確認するのはとてもやさしいでしょう。しかし、すべての日の1秒ごとに調べるのはばかげています!

2番目の問題は、挿入あるいは削除でデータの配列構造が再編成されれば、インデックス番号が変わり、findByDateオブジェクトが正しくないリンクを持つことです。例えば、配列の3番目の項目("5/22/2003"の日付)を削除すれば、項目4が項目3(配列の最後の項目)になります。しかしfindByDate.value("5/23/2003")は、入力のインデックスが変更されたことを知らないので、そのまま4を返すでしょう。

さて、我々は配列を毎回修正してfindByDateのdictionaryを再編成することで、2番目の問題を解決することができるでしょう。しかし、それはとても時間がかかります。もっと簡単な解決策は、各入力に決して変わることのないユニークなIDを持つことを設定し、配列の添字の代わりにそのIDへリンクするのです。しかしそれはまた別の問題を生じます――我々はIDによって入力を検索する方法が必要です。ちょっと待ってください!配列の代わりに、データ構造のためにdictionaryオブジェクトを使うだけでは駄目なのでしょうか?それは配列のようにデータの追加、削除ができるにもかかわらず、どの入力もユニークなkeyでインデックスされ、IDによって特定のレコードを瞬時に検索することができます。

しかしながら、依然として第一の問題は残ったままです。私は解決策を見出しましたが、あなたならどのように解決するでしょうか?

次回までそれについて考え、あなたのアイデアを私のものと比較してみてください。それを今回の宿題にします。

次週

RBlogのデータ構造のデザインを終了し、我々が考え出した解決策を検討します。

Letters

今日はPaul RushtonさんからOOPに関する素晴らしい質問をいただきました。彼は次のように書いています。

Marcさん。私はあなたのコラムからとてもたくさんのことを学びました。 私は毎週コラムに目を通して、RBDも購読しています。どちらも素晴らしい内容です。よいお仕事をこれからもお続けください。私は初心者で、RBを使ってプログラミングに関する学習を楽しんでいます。

私の質問は、「constructor」と「destructor」に関係してです。いくつかの記事、特にパス・ファインダー・プロジェクト(RBD 第1.5号)とアンドゥ・プロジェクト(RBD 第1.2号)の中で、どちらもこれらの概念を用いていました。私は今取り組んでいるプロジェクトの中でそれを使用したいと考えているので、特にアンドゥの記事について勉強しています。私はただそれをコピーするだけで動作させることができますが、私が何をしているのか、その理由についても理解したいのです。将来のあなたのコラムのQ&Aの中で、これらの使用と目的を説明するため、時間を割いて頂けないでしょうか?

Paul Rushton

Paulさん、とてもいい質問です。この質問を投稿して頂いてありがとうございます!

Scott Forbes氏の記事「アンドゥを行う」(RBDの第1.2号)は、OOPのとてもよい例です。記事を読んでいない人のために、簡単に説明しましょう。アイデアは、すべてのプロジェクトにただドロップするだけで、再利用可能な柔軟なアンドゥ・システムを作成することです。このシステムによって、listboxの中の項目を並べ替えるように、checkboxはどのように自身をアンドゥするか知ることができるでしょう。このように、究極の「アンドゥ可能」なオブジェクトは、どのように自身をアンドゥするかを知っているオブジェクトのことです。

しかし、その記事が素晴らしいのは、Scott氏がすべての答えを提供してはいないところです。その代わりに、いろいろなデータ構造のオプションに対して、私がいまちょうどここで行っているようにいろいろなアンドゥ・システムの概念を紹介しています。Scott氏は、各メソッドの実装をデモし、それから各々の落とし穴を明らかにします。

1つの例を挙げましょう。一回のアンドゥのために、各オブジェクトがその前の状態を記憶していたのでは、一つのオブジェクト以外はメモリーの無駄になる(一つのオブジェクトだけがアンドゥするために選択されるだけです)ことをScott氏は指摘しています。それからScott氏は、各オブジェクトのアンドゥ情報を追い続けるマスター・コントロール・オブジェクト(「ディレクター」)を備えたよりよいシステムを考え出しました。これが機能するには、動作が起きたときに各オブジェクトが特定のフォーマット(UndoableActionクラス)でその動作を保存し、そのオブジェクトをディレクターに渡します。後で、ユーザがアンドゥする動作を選択したら、ディレクター・オブジェクトは、その動作をアンドゥするためにそのUndoableActionオブジェクトを呼び出したオブジェクト(checkboxlistboxなど)にコマンドと共に送り返します。

もちろん、これが機能するにはディレクターは二つの情報について知る必要があります。一つは、保存するためのUndoableActionオブジェクトです。二つ目に、どのオブジェクトがそれを送ったかです――そうでなければ、それをどこに送り返せばよいのか分かりません。

その情報はつねに必要とされているので、senderと呼ばれるプロパティとしてUndoableActionオブジェクトの内部にそれを保存します。そうすれば、ディレクター・オブジェクトは、UndoableActionオブジェクトをどこへ返すか知るために、senderを調べるだけですみます!

しかし、senderUndoableActionのプロパティであるだけなので、あなたはその設定を忘れるかもしれませんね?そこでScott氏は、そのパラメータとして呼び出しオブジェクトを必要とするconstructorメソッドを使用します。

これは、UndoableActionオブジェクトを作成するときに、どのオブジェクト(通常は現在のオブジェクト)がアクションを作成したのかを知らせることを要求されることを意味します。

  
anUndoAction = new Undoableaction(me)

分かりますか?

constructorは単なる初期化メソッドです。それはオブジェクトが作成されるときに呼ばれます。destructorはそれとは反対で、それはオブジェクトが破棄されるときに呼ばれます。これで新しいオブジェクトを初期化し、それが消える前にオブジェクトを片づけることができます。REALbasicでは、ほとんどのオブジェクトでOpenCloseイベントがあり、それはconstructordestructorメソッドです。

REALbasicでは、それをクラスと同じ名前にすることで、constructorメソッドを作成することができます。例えば、testClasstestClassと呼ばれるメソッドを持つならば、そのメソッドは新しいオブジェクトが作成されたときに呼ばれます。この動作を確認するためには、testclass.rbをダウンロードしてください。

REALbasicでdestructorを作成するには、~testClassというようにメソッドの名前(これもクラスの名前)の前にチルダ(~)を挿入します。メソッドは、オブジェクトが破棄されるときに呼ばれます。Destructorはパラメータは不用です。

詳細

REALbasicのdestructorconstructorに関する動作について、覚えておくべき重要なことがいくつかあります。

まず始めに、constructorはウィンドウに置いても呼ばれません――それはnewコマンドで初期化された時にだけ呼ばれます。それらはコントロールのサブクラスでも機能しません(コントロールはOpenCloseイベントがあるので、こちらを使います)。

二つ目に、constructorは一連のオブジェクトの中で一度だけ呼ばれます。例えば、myTestClasstestClassのサブクラスで、どちらもconstructorを持つならば、サブクラスのオブジェクトが初期化された場合にはmyTestClassのものだけが呼ばれます。

しかしながら、destructorにとってはその逆が当てはまります。つまり、サブクラスが破棄されるときに、サブクラスのdestructorと親オブジェクトのdestructorが順に呼ばれます。

私はOOPのエキスパートではありませんので、REALbasicがconstructordestructorをサポートしているのかさえ知りませんでした(私はどこに説明されているか知りません――Matt's bookでそれを見つけました)。昔、初期化が必要なオブジェクトがあったときに、私は自分のinitメソッドを作成しました。しかしもちろん、それはオブジェクトを作成するものすべてが、initルーチンを忘れずに呼ばなければならないことを意味します。初期化ルーチンを呼び忘れる心配がないので、constructorの方がはるかに優れています。


RBU-Jの通知サービス!コラムが発表されるたびに日本語版REALbasic Universityのお知らせの emailがあなたに届きます。登録・削除は ここ から。

INDEXに戻る