アクセスの抽象化
銀行口座を例に考えてみよう。それぞれの利用者が口座を開設していて、その口座番号に紐づく『金庫』にお金を預けたり、引き出しているはずだ。そして、一般的には(銀行の関係者ではない限り)『金庫』の内部詳細は隠されているが、それを気にすることなくサービスを利用することができる。
『金庫』へのアクセスを抽象化している
といえる。
抽象化することで、利用者にとっては口座番号というインターフェイスさえ知っていればサービスを使うことができる。また、銀行は金庫の内部詳細を説明しなくとも(場合によっては突然のシステム刷新をしても)サービスを提供し続けることができる。
では、ここまでの話をモデル化してみよう。
インスタンスハンドル
まず利用者は、口座を開設すること、すなわち口座番号を発行することから始める。
この口座番号にあたるユニークな番号のことを ハンドル と呼ぼう。そして、サービスの窓口となる口座は、口座番号ごとに割り振られるインスタンス である。つまり、いま1つのインスタンスにつき、1つのハンドルが紐づけられて管理されたことになる。
このように、インスタンスに対して割り当てられるユニークなハンドルをここでは、インスタンスハンドル と呼ぶことにする。
疑似コード
それでは、簡易版 銀行システムのAPI を考えてみよう。
ハンドルを生成する
// 例:口座を開設して、口座番号を発行する
using handle = uint64_t;
const handle kouzabangou = ginko::create_kouza();
ハンドルの型は必要な数を表現できるのであれば何でもよい。(上の例では、uint64_t を使った。)ハンドルの生成と同時に、システム内部ではハンドルに紐づけるためのインスタンスを新たに生成する。
内部システム要件
- 新規発行のたびにユニークなハンドルを生成する
- 発行したハンドルから対応するインスタンスを検索するためのテーブルを持つ
※内部システムの実装コードは今回の本質ではないため、取り扱いません。
ハンドルでアクセスする
// 例:口座の預金額を取得する
using kingaku = uint64_t;
const kingaku yokingaku = ginko::get_yokingaku( kouzabangou );
// 例:口座に1万円を入金する
ginko::nyukin( kouzabangou, 10000 );
サービスを利用するときは、必要な引数に加えて、インスタンスを特定するためのハンドルを指定する。(他人の口座に入金してしまわないように…。)
ハンドルを破棄する
// 例:口座を解約する
ginko::kaiyaku( kouzabangou );
ハンドルの破棄と同時に、システム内部のテーブルによって紐づけられていたインスタンスを削除する。
ハンドルの有効性を確認する
不正な値を持ったハンドルや、1度破棄してしまったハンドルは、有効なインスタンスを導けない可能性があり深刻な問題を引き起こす。このように不正なハンドルを使った場合に、それぞれのサービスは処理を中断して、適切なエラーを返す必要がある。もしくは、利用者が事前にハンドルの有効性をチェックするためのサービスを用意しておくことでも回避できる。
// 例:口座番号が正当なものか確認する
const bool is_available = ginko::is_available_kouzabangou( kouzabangou );
必要性
C++ に限らず、オブジェクト指向言語によって設計するサービスはクラスの集合で構成される。インスタンスが互いにメッセージを送りあうこと、つまりメンバ関数(あるいはメソッド)をコールしあうことで強く依存して動作している。一方で、リリースされてなお、日々のメンテンナンスやバージョンアップを繰り返しながら、エンドユーザからみえる姿はそのままに、その内部設計を大胆に変更するサービスは多く存在する。これらのサービスは内部実装とインターフェイス、いわゆるAPI との設計に強い依存関係があってはならない。このようなときに、今回ご紹介したインスタンスハンドルの考え方はとても効果的に働く。わかりやすく、よく知られたテクニックなので、使える場面であれば極力使っていきたいものである。