##はじめに
0/2 はじめに void *
1/2 クラス表現と継承⇒この記事の再考です。
2/2 インターフェイス, オーバーライド
ちょうど2ヵ月前、Cでのオブジェクト指向・デザインについて真面目に考えだして、Qiitaに上記記事を投稿しました。
で、2ヵ月色々と趣味開発をしてみて今見返した感想。「気持ちはわからんでもないけど使いにくいな~」
現にあそこで書いたようなクラス表現、自分のコードで1個も使っていないです。それはvoid *に対する考えと同じで新しい知識でupdateされた結果なんですが。
ということで、再考。改めてもっと使いやすい表現について考えます。
##以前の考え:3行で
- publicメンバーの表現方法を考えよう。
- privateメンバーはマクロをうまく使って表現しよう。
- 継承は不向きなので除外
これを今見返した感想です。
そういう考えもありだとは思うけど、まだ**「オブジェクト指向を表現しないと」**という気持ちが強い気がします。特にpublicメンバーの扱い。
こんなに頑張って公開したいメンバー、そんなにあるかな?あるなら自分の実装でバンバン使うはず
ということで、自分の実装も見返した今の考えを整理
##今の考え:3行 4行で
- privateメンバーは前方宣言で表現がいい
- 継承やpublic/private分け、マクロで頑張って表現するくらいなら基本集約でいい
- メソッドは基本this付きのAPIでいい
- 継承表現が強いケースもある。継承はなしと決めつけずに柔軟な選択を
2018/10/06 4行目追記
以下詳細です。
###privateメンバーは前方宣言で表現がいい
以前こちらの記事で前方宣言というテクニックについて記載しました。同記事の最後にも記載したんですが、これを利用したクラス表現が個人的に一番分かりやすいんですよね。
struct state_manager_t;
/** @brief StateManager class definition, to management state*/
typedef struct state_manager_t *StateManager;
StateManager state_manager_new(size_t state_info_num, const state_info_t * state);
void state_manager_set_state(StateManager this, int state);
int state_manager_call(StateManager this, void *arg);
void state_manager_free(StateManager this);
上記なら操作はset_state. call。生成と削除がnew/freeとクラス図もイメージ出来るし、もうこれでいいじゃん。(※個人の感想です
###継承やpublic/private分け、マクロで頑張って表現するくらいなら基本集約でいい
さて、privateメンバーはうまく表現できたけど、publicメンバーや継承はそうはいきませんよ。継承は不向きだからいいとしてpublicなメンバー。ライブラリAPIでもよく出てくるから、ライブラリを作ってたならきっと綺麗に表現したんでしょうね。
さて、昔の私はどうpublicを表現したのか。
例えばこちらのstate_machine, FD登録可能なスレッドにstate machineを登録して動作させる仕様です。
FD登録可能⇒state machineと同じスレッドにイベント用FD登録したかったりするので、スレッド情報をpublicメンバーとして扱いたい。
こちらの定義、私自身がどうやったかというとこうです。
struct state_machine_t;
/** @brief StateMachine class definition */
typedef struct state_machine_t *StateMachine;
typedef struct state_machine_info {
StateMachine state_machine;
int thread_num;
} state_machine_info_t, *StateMachineInfo;
…publicで公開したいメンバーthread_numはヘッダーに記載してますが、privateメンバーは全て前方宣言で隠ぺい。隠ぺいしたクラスを所持する形で表現しています。
実質これがprivateメンバーですよと思えば、こういう表現もありですね。
マクロまで使ってpublic/privateを表現する方法、1度も実装で使ってないんですよね。
それよりもこうやってヘッダーにはすべての所在を明らかにして、隠ぺいしたい箇所をクラス定義して所持・メンバーを隠ぺいとした方がはるかに書きやすいしわかりやすいという結論に落ち着きました。
オブジェクト指向言語でもないのに書き方まで真似する必要はなくて、「このクラス図はこんなデータ所持の仕方で表現します」って指針さえあればそれでいいんですよ。きっと
クラス図も言語もどちらもただの道具なんだから、その設計の利点がちゃんと表現できていれば、表現の手段はこちらに都合のいいように解釈しちゃえばいいんです(※個人の感想です。
ただ、改めてオブジェクト指向について勉強していると100%集約で利点が表現できるなんてことはなかったので、そこはケースバイケースで。
特にインターフェース
###メソッドは基本this付きのAPIでいい
インターフェイスに関しての考えは依然変わらず。基本はAPIで、以下に関してはメソッド表現の方がわかりやすい。
- インターフェイスを実際に使用する側が実体を全く意識しなくていい場合
- dlopenによる静的ライブラリのプラグイン追加
詳細はこちらを参照ください。
###継承表現が強いケースもある。継承はなしと決めつけずに柔軟な選択を
2018/10/06 追記
"インターフェイスを実際に使用する側が実体を全く意識しなくていい場合"、もう少し深堀して考えると
インターフェースクラスは継承を利用したデザインが有効なこともあるという考え方も出来ます。
例えばFactory Methodパターンを利用して生成部分を抽象化するケース。
これは抽象に依存させるためのうまい表現なので、活用出来るシーンも沢山考えられます。
というわけで、C言語でも継承表現を使うことが最善なケースは十分あると考えを改めました。
なので、継承は要らない!と決め打ちせず、表現の複雑さ vs 抽象化のメリットを比較した上で上手により有効な道具を選んで使っていきましょう。
##最後に
言語もUMLもデザインも全て道具の1つなので、自分たちが使いやすいように利用すればいいんですよ。
表現が完全一致しているかどうかは些細な問題ですよきっと。
ガンガン色々取り入れて実践すると、使いにくいものは使わなくなるから分かりやすいですね。
ただし、以下2パターン考えられるので、日々知見を広げ、自身の考え方もアップデート出来るようにしましょう。(戒め
- 使わない⇒使いにくい
- 使わない⇒力量が足りず使えていないだけ
コメントいただいた@tenmyoさんや@shiracamusさん等、ここ1~2ヵ月で色々な方の考え方に影響を受けています。
皆さんありがとうございます。