Niftyの過去ログ集 - 宴会部長と Private Interface パターン
宴会部長と Private Interface パターン
Private Interface パターンってどれぐらいの人が知っているでしょうか.これは C++ のプライベート継承を有効に使ったパターンです.このパターンには感心してしまいました.継承にアクセス権があるという C++ の言語仕様はあなどれません.
00955/00955 BYI20012 まさーる RE^2:協調場モデル ( 6) 98/01/18 17:21 00953へのコメント Junichi Suzuki さん、こんにちは。 >あと,Decoratorパターンを適用するのも面白い手だと思いますが, >どうでしょうか? > all 協調場モデルは読んでませんが、まこたんさんの解説から察するに、これは宴会 が始まったら宴会部長DecoratorをBさんに引っ付けて、終わればまたその宴会部 長Decoratorをとるってことですよね。 次のPrivate Interfaceパターンなんてどうでしょう。こっちの方が簡単かも: class 宴会部長; class 課長; class 平社員; class 宴会の場 { public: void 宴会部長として登録(宴会部長&); ... }; class 会社にて { public: void 課長として登録(課長&); void 平社員として登録(平社員&) ... }; class Bさん : private 宴会部長, private 課長 //publicじゃないよ { public: void 宴会がはじまるぞ!(宴会の場& e) { e.宴会部長として登録(*this); } void 今日も会社かあ(会社にて& k) { k.課長として登録(*this); } }; private継承を使うことによって、普段Bさんを見たら宴会部長には見えないんだ けど宴会がはじまればBさん自ら宴会部長は俺だ!と主張します (これがPrivate Interfaceパターン)。 継承にもアクセス権があるというC++の特徴を利用したパターンですね。 #以前FCで紹介したら評判悪かったパターンなんですけどね 98/01/18(日) まさーる(BYI20012)
このパターンは Object Menter の James Newkirk によるものです.
00967/00968 BYI20012 まさーる Private Interface ( 6) 98/01/19 22:49 00961へのコメント Junichi Suzuki さん、こんにちは。 >>継承にもアクセス権があるというC++の特徴を利用したパターンですね。 > >なるほど,これはシンプルで良いかもしれない….private継承する例で >まともなものを始めて見ました;-). 僕もです。これから考えると、いままで意味不明と言われ続けてきたprotected 継承への応用もありえそうです(Bさんの子供にも実は宴会部長の資質がある、 とか)。 >ところで「Private Interface」は,どなたかが提案されている >ものなのですか? Object MenterのJames Newkirkです。 詳しくは http://www.oma.com をご覧ください。 98/01/19(月) まさーる(BYI20012)
実は,最初このパターンを知ったときに C++ がこういう仕様であることを知らなかったため,不安になってC言語フォーラムで質問したのでした.今度は C言語フォーラムからの引用です.結局 C++ の仕様は厳密に言うと
実装のみの継承 = プライベート継承
ではない,ということですよね.
03028/03028 BYI20012 まさーる private継承のup-cast (13) 97/08/22 11:27 みなさん、こんにちは。まさーると申します。 えーっと、クラスのprivate継承について質問なんですが、こいつを使うと型の 継承はせずに実装を継承するだけだ、とずーっと思ってましたが、厳密にはそう じゃないんでしょうか? 例えば、 class Interface { public: virtual void method() = 0; }; void func(Interface* i) { i->method(); } class Target : private Interface //Interfaceからのprivate継承 { public: void method2() { func(this); } //TargetからInterfaceへのup-cast? private: void method() { cout << "Target::method"<< endl; } }; int main() { Target t; //func(&t); <---もちろんこれはエラーになる。 t.method2(); return 0; } というソースはVC++5.0でちゃんと通ります。 つまりprivateがアクセスできるところではprivate継承の親クラスへのup-castが 可能と思っていいんでしょうか? 97/08/22(金) まさーる(BYI20012)
C言語フォーラムで Private Interface パターンを紹介したときの発言は,以下の通りです.uekenさんには不評でしたが・・.
03040/03040 BYI20012 まさーる RE^2:private継承のup-cast (13) 97/09/02 23:07 03038へのコメント ueken さん、はじめまして。 もう完全にあきらめてましたが、レスしてくれてどうもありがとうございます。 >「up-cast」ってどんな意味なんでしょう? 親から子へのキャストをdown-castというので、その逆、つまり子から親へのキ ャストの意味で使いました。一般的な言葉じゃなかったです。ごめんなさい。 > t.method2(); >は、privateによる継承により、親クラスのmethod()が見えないので、外部関 >数func(Interface*)から、回り道をして自分のクラスのTarge::method()を呼 >んでいるだけでは?こんな遠回りする理由が私には思い付きませんが... 確かに一見無意味なコードですが、これができるとある場面で 「特定のオブジェクトにだけインターフェイスを公開したい。でもみんなは いや」 ということがあの悪名高いfriendを使わずにできるんです。関数のかわりにクラ スにして class InterfaceHandler { Interface* mInterface; public: InterfaceHandler(Interface* i) : mInterface(i) {} void DoIt() { i->method(); } }; とし、Targetクラスを class Target : private Interface { void method() { cout << "Target::method" << endl; } public: InterfaceHandler* DoMakeInterfaceHandler() { return new InterfaceHandler(this); } }; とすれば、 int main() { Target t; InterfaceHandler* h = t.DoMakeInterfaceHandler(); h->DoIt(); return 0; } とできるのでInterfaceHandlerはあたかもTargetのプライベートなmethodにアク セスできています。しかしこのmethodは他から呼ぶことはできません。どうで す?使えそうでしょ? これは、「Private Interface」のパターンというものでJames Newkirkさんの論 文で知りました。この論文はhttp://www.oma.com から入手できます。C++の他 に、Javaによる実装例も載っています。 >私の理解では、privateによる継承は、I/Fを継承せずにコードを再利用する。 >また、親クラスへのキャストを禁止することだと理解してます。 ぼくもそう思ってましたが、どうもC++では継承関係もprivateやpublicに制御さ れるアクセス権を持つ、ということらしいです。Private継承にこんな利用法が あったとは驚きです。 97/09/02(火) まさーる(BYI20012)
もう少し補足説明もしておきました.
03045/03046 BYI20012 まさーる RE^3:private継承のup-cast (13) 97/09/03 22:46 03042へのコメント ueken さん、こんにちは。 >なるほど、friendを利用したくないという好みがあるんですね。 >「特定のオブジェクトにだけI/Fを公開する」というのは、 >実装レベルの場合、特定のクラスからならアクセス可能という >ことなのかな? というか、Targetクラスが実行時に許したオブジェクトだけがアクセス可能にな りますね。たしかにインターフェイスのクラスが一つ増えますが、friendを利用 してないのでカプセル化が破られていないのと、クラスの依存関係がすっきりす るところがこのパターンのいいところだと思います(InterfaceHandlerはTarget のことを知らなくてすむ)。 uekenさんの例ではfriendを使ってるし、TargetUtilとTargetが相互依存してい るので各クラスがもっと複雑になり数が多くなったとき問題になるのではないで しょうか。このあたりはカプセル化が破られるとどうなるか、ということと同じ です。 >何か見落としているようなので、ちょっと、 >例の論文を読んでみる必要がありそうですね... ぜひ読んでみてください。この論文は、Gang of FourのDesign Patternsと同じ 形式で書かれているのでわかりやすいです。 このPrivate Interfaceパターンは、CommandパターンやMementoパターンを使う とき威力を発揮すると思います。 実は、GUIアプリで各コマンドのUndoの機能をいれるのにDocumentのインターフ ェイスを公開すべきかfriendにすべきかかなり悩んだことがあって今思えばこの パターンをバシバシ使えばよかったんだな、と思ってます。 97/09/03(水) まさーる(BYI20012)
なお冒頭にある宴会部長サンプルは friend を使って次のようにも書けます.
00993/00993 BYI20012 まさーる Private Interface 2 ( 6) 98/02/06 01:12 00955へのコメント 今日comp.objectを見てたら、こんなのもありみたいですね。 (あまりきれいじゃないですが、Robert Martinの発言をアレンジして) //------------------------------------------------------- class 宴会部長 { private: virtual void 乾杯の音頭をとる() = 0; ... friend class 宴会の場; }; class 課長 { private: virtual void 朝礼のあいさつをする() = 0; ... friend class 会社にて; }; class 宴会の場 { public: void じゃそろそろ始めましょか(宴会部長& e) { e.乾杯の音頭をとる(); } }; class 会社にて { public: void 朝礼はじまりますよ(課長& k) { k.朝礼のあいさつをする(); } }; class Bさん : public 宴会部長, public 課長 { public: ... private: virtual void 乾杯の音頭をとる(); virtual void 朝礼のあいさつをする(); }; int main() { Bさん b; 宴会の場 e; e.じゃそろそろ始めましょか(b); .... 会社にて k; k.朝礼はじまりますよ(b); return 0; } //------------------------------------------------------- 今度は、Bさんは主張せずとも場だけが知っているってところでしょうか。 98/02/06(金) まさーる(BYI20012)
参考資料
- Private Interface: A design pattern for controlling visibility, James Newkirk, PLoP-97