インターフェイスこそが設計の鍵
そもそもインターフェイスと実装を分離する理由はインターフェイスを規定することで結合を可視化して制御しやすくすることにある。
お仕事ではインターフェイスを規定せずに好き勝手に呼び出すコードを多々見かける(そしてウンザリする)が、そういうコードは修正が全体に及ぶため毎度毎度コードの断片を移植して作り直す羽目になっている。これはインターフェイスをきちんと設計することで回避可能な愚行と言っていいだろう。
C++ではインターフェイスと実装を分離するパターンはいくつかある。基本的には多態を使うが継承関係を逆にした方が使いやすい場合もある。
インターフェイスを継承して実装する
教科書通りの方法がこれ。各実装はインターフェイスの一種として扱われる。当然public継承。
実装を継承してインターフェイスを付け足す
上と真逆なので奇妙に思えるかもしれないがこの方法だと仮想関数を使わずに済むのでオーバーヘッドが皆無というメリットがある。
ただし、依存関係が固定的なので実装を隠すことしかできず、そのあたりの柔軟性が必要な場合には向かない。おそらくシングルトンに向いていて、例えば機種依存部分を隠蔽したりする。一般的にはprivate継承を使う。
ほぼ同じことは多態でも実現できるが、この方法であればオーバーヘッドなしで実現できるので関係が固定ならこの方法を使う方がメリットがある。実体を必要としないので実装とインターフェイスをソース上で分離しつつ、シングルトンのためにインスタンスを用意するというバカバカしい状況を回避できる。
実装をテンプレートのインターフェイスへ継承する
これも実装を継承するパターン。単に実装を継承するより柔軟性が高くなる。使う場面は大きく分けて二つ。
- 型の付与
リストやスタックのアルゴリズムを例えばvoid*で記述しておいてテンプレートで型を付与することで、テンプレートのみで書いた時のようにコードがコンパイラによってコピペされてしまうのを防げる。
この使い方ではほぼprivate継承しか使わない。
* ダックタイプ適用しようとするクラス群が同じメソッドのセットを持っているなら多態を使わずに依存性注入のようなことができる。静的依存性注入とでも言うところか。
例えばいろんな種類のロックをRAIIで統一的に使うときにはこの方法を使う。オーバーヘッドがないのである種のロックのようにプロセッサの一命令で済んでしまうような非常に軽い操作にも使える。
一般的にはprivate継承で使う。