BOOK Preview
|
|
|
6.1.3 ADTのその他の例
原子炉の冷却装置を制御するソフトウェアを書いているとしよう。そのための操作を次のように定義すると、冷却装置をADTとして扱えるようになる。
coolingSystem.GetTemperature()
coolingSystem.SetCirculationRate( rate )
coolingSystem.OpenValve( valveNumber )
coolingSystem.CloseValve( valveNumber )
これらの操作を実装するためのコードは、制御システムが導入される環境によって異なる。プログラムの残りの部分は、これらの関数を使って冷却装置を操作することができ、データ構造の実装や、データ構造の制限、変更といった内部の詳細から解放される。
次に、ADTとそれらについて考えられる操作の例をまとめてみよう。
メニュー | ヘルプ画面 | スタック |
新しいメニューを開始する メニューを削除する メニュー項目を追加する メニュー項目を削除する メニュー項目を有効にする メニュー項目を無効にする メニューを表示する メニューを非表示にする メニュー選択を取得する |
ヘルプトピックを追加する ヘルプトピックを削除する 現在のヘルプトピックを設定する ヘルプ画面を表示する ヘルプ画面を消去する ヘルプの索引を表示する 前の画面に戻る |
スタックを初期化する スタックに項目をプッシュする スタックから項目をポップする スタックの先頭の項目を読み取る |
ファイル | ミキサー | ポインタ |
ファイルを開く ファイルを読み取る ファイルに書き込む 現在のファイル位置を設定する ファイルを閉じる |
電源を入れる 電源を切る 速度を設定する 瞬間粉砕を開始する 瞬間粉砕を停止する |
新しいメモリへのポインタを取得する 既存のポインタに割り当てられたメモリを解放する 割り当てられたメモリの量を変更する |
エレベータ | リスト | 燃料タンク |
1つ上の階へ移動する 1つ下の階へ移動する 指定された階へ移動する 現在の階を報告する 1階に戻る |
リストを初期化する リストに項目を挿入する リストから項目を削除する リストから次の項目を読み取る |
燃料を補充する 燃料を排出する タンクの容量を取得する タンクの状態を取得する |
自動速度制御装置 | 電灯 | |
速度を設定する 現在の設定を取得する 以前の速度に戻す解除する |
明かりをつける明かりを消す |
これらの例から、いくつかのガイドラインが導かれる。次は、これらのガイドラインについて見ていこう。
■ 一般的な下位レベルのデータ型を、そのデータ型のままでなく、ADTとして作成または使用する
ADTのほとんどの議論は、一般的な下位の実装レベルのデータ型をADTとして表すことに焦点を合わせている。例からわかるように、スタック、リスト、キューだけでなく、ほぼすべての一般的なデータ型をADTとして表すことができる。
そこで、「このスタック、リスト、またはキューは何を表すのか」について検討する必要がある。スタックが社員の集合を表すのであれば、ADTをスタックではなく社員として扱う。リストが請求書の控えを表すのであれば、それをリストではなく請求書の控えとして扱う。キューがスプレッドシートのセルを表すのであれば、キューの汎用的な項目ではなくセルとして扱う。このように、最上位レベルの抽象化を楽しもう。
■ ファイルなどの一般的なオブジェクトをADTとして扱う
ほとんどのプログラミング言語には、おそらくあなたも知っているであろうADTがいくつか含まれているが、それらがADTだとは気付いていないかもしれない。ファイルの操作はその好例である。ディスクに書き込みを行う際、面倒な処理はオペレーティングシステムが肩代わりしてくれるので、わざわざ読み取り/書き込みヘッドを特定の物理アドレスに移動したり、ディスクセクタを使い果たしたら新しいディスクセクタを割り当てたり、謎のエラーコードを解読したりする必要はない。オペレーティングシステムは、第一段階の抽象化とそのレベルのADTを提供する。そして高級言語が、第二段階の抽象化とそのレベルのADTを提供する。高級言語を利用すれば、オペレーティングシステムの機能を呼び出したり、データバッファを操作したりといった面倒な作業から解放され、ディスク領域のかたまりを「ファイル」として扱うことができる。
ADTも同じように階層化できる。データ構造レベルの操作(スタックのプッシュ、ポップなど)を提供するレベルでADTを使用したければ、それでもかまわない。その層の上に、現実世界の問題というレベルで操作を行う新しいレベルを設けてもよい。
■ 単純な項目でもADTとして扱う
ADTを使用するのは、複雑きわまりないデータ型と決まっているわけではない。先ほどの例の中に、たった2つの操作(明かりをつける、明かりを消す)をサポートする単純なものが1つある。単純な「オン/オフ」処理をルーチンとして独立させるのは無駄に思えるかもしれないが、ADTはこのような単純な操作にも効果的である。電灯とその操作をADTにまとめると、コードが読んでわかる自己解説コードのような変更しやすいものになり、変更の影響の及ぶ範囲がTurnLightOn()ルーチンとTurnLightOff()ルーチンに限定され、やり取りされるデータ項目の数も少なくなる。
■ ADTの名前を格納媒体から独立させる
保険料率表があまりに大きいので、常にディスクに格納しているとしよう。これを「レートファイル」と名付け、RateFile.Read()といったアクセスルーチンを作成できるようにしたいと考えている。だが、それを「ファイル」として参照してしまうと、データに関する情報を必要以上に外部にさらすことになる。保険料率表をディスクではなくメモリに格納するようにプログラムを変更した場合、それをファイルとして参照するコードは不正確になり、誤解を生み、紛らわしくなる。そういう場合は、クラスやアクセスルーチンの名前をデータが格納される方法から切り離して、「保険料率表」といった名前のADTとして参照させる。そうすれば、クラスやアクセスルーチンの名前は、rateTable.Read()、またはrates.Read()といった単純なものになる。
6.1.4 オブジェクト指向以外の環境における、ADTによる複数のインスタンスの処理
オブジェクト指向言語は、ADTの複数のインスタンスを自動的に処理する。これまでオブジェクト指向環境しか使ったことがなく、複数のインスタンスの実装上の詳細を処理する必要に迫られた経験がないとしたら、これまでの幸運に感謝しよう(そして、6.1.5に進んでかまわない)。
Cのようなオブジェクト指向でない環境で作業している場合は、複数のインスタンスを処理するための環境を整えなければならない。これは一般的に、インスタンスを生成または削除するためのADTのサービスを追加したり、複数のインスタンスに対応するようなADTのサービスを設計したりすることを意味する。
フォントのADTが、最初は次のサービスを提供していたとしよう。
currentFont.SetSize( sizeInPoints )
currentFont.SetBoldOn()
currentFont.SetBoldOff()
currentFont.SetItalicOn()
currentFont.SetItalicOff()
currentFont.SetTypeFace( faceName )
オブジェクト指向でない環境では、これらの関数はクラスに属さず、むしろ次のようなものになる。
SetCurrentFontSize( sizeInPoints )
SetCurrentFontBoldOn()
SetCurrentFontBoldOff()
SetCurrentFontItalicOn()
SetCurrentFontItalicOff()
SetCurrentFontTypeFace( faceName )
一度に複数のフォントを操作したい場合は、次に示すように、フォントのインスタンスを生成したり削除したりするためのサービスを追加する必要がある。
CreateFont( fontId )
DeleteFont( fontId )
SetCurrentFont( fontId )
fontId(フォントのID)は、フォントのインスタンスを複数生成して使用する場合に、それらを追跡するための手段である。他の処理については、ADTのインターフェイスの処理方法として次の3つのオプションのいずれかを選択できる。
■ オプション1:ADTのサービスを利用するたびにインスタンスを明示的に指定する
この場合、「現在のフォント」という概念はない。フォントを操作するすべてのルーチンにfontIdを渡す。基本的なデータはフォントルーチンが管理するので、クライアントコードはfontIdを追跡するだけでよい。そのためには、各フォントルーチンの引数としてfontIdを追加する必要がある。
■ オプション2:ADTのサービスが使用するデータを明示的に提供する
この方法では、ADTのサービスを使用するルーチン内部に、ADTが使用するデータを宣言する。つまり、ADTの各サービスルーチンに渡すFontデータ型を作成する。ADTのサービスルーチンは、呼び出しのたびに渡されたFontデータを使用するように設計されていなければならない。この方法を使用する場合、クライアントコードにfontIdは必要ない。なぜなら、Fontデータはクライアントコードで管理されるからだ(データはFontデータ型として直接提供されるが、それにアクセスするのはADTのサービスルーチンに限定すべきである。これは「閉じた」構造を保つために必要だ)。
この方法の利点は、ADTのサービスルーチンがfontIdに基づいて情報を参照する必要がないことである。欠点は、Fontデータがプログラムの関係のない部分にまで公開されてしまい、ADTによって隠ぺいされるはずの実装上の詳細が、クライアントコードで使用される可能性が高くなることである。
■ オプション3:暗黙のインスタンスを(十分に注意して)使用する
SetCurrentFont( fontId )など、特定のフォントインスタンスを現在のインスタンスとして設定するために、新しいサービスを設計する。現在のフォントを設定すれば、他のすべてのサービスは呼び出されたときに現在のフォントを使用するようになる。この方法を利用すれば、他のサービスへの引数としてfontIdを使用する必要はなくなる。簡単なアプリケーションでは、これによって複数のインスタンスを合理的に使用できるようになる。ただし、システム全体が状況に依存することになるので、複雑なアプリケーションでは、フォントルーチンを使用するコードの始めから終わりまで、現在のフォントインスタンスを追跡しなければならない。この方法では複雑さが増大する傾向がある。どのような大きさのアプリケーションにとっても、これよりも良い方法が存在するはずだ。
オブジェクト指向でない言語を使っている場合、ADTの内側では複数のインスタンスを処理するためのオプションが揃っているが、ADTの外側では選択肢はこの3つに限られる。
6.1.5 ADTとクラス
ADTはクラスの概念の土台となる。クラスをサポートするプログラミング言語では、ADTをそれぞれ専用のクラスとして実装することができる。通常、クラスには他にも継承やポリモーフィズムという概念がある。クラスは「ADT+継承およびポリモーフィズム」として考えることもできる。
INDEX | ||
Code Complete 第2版 上・下 | ||
第6章 クラスの作成 | ||
1.6.1 クラスの基礎:抽象データ型(ADT)(1) | ||
2.6.1 クラスの基礎:抽象データ型(ADT)(2) | ||
3.6.2 良いクラスインターフェイス(1) | ||
4.6.2 良いクラスインターフェイス(2) | ||
5.6.3 設計と実装の問題(1) | ||
6.6.3 設計と実装の問題(2) | ||
7.6.4 クラスを作成する理由 | ||
8.6.5 言語固有の問題/6.6 クラスを超えて:パッケージ/6.7 参考資料/6.8 まとめ | ||
「BOOK Preview」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|