- - PR -
データクラスの設計とダウンキャストの回避について
投稿者 | 投稿内容 | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
投稿日時: 2007-09-17 14:10
以下構成(C++)でデータクラスの設計をした場合に
ダウンキャストの回避方法があるかご相談させて下さい。 まず、2つのデータクラスと基底クラスがあるとします。 class SuperVo { public: mapping(map<string,string>)=0; // この2つのメソッドをサブクラスで equals(SuperVo*)=0; // 実装させたいための基底クラス } class SubAVo : public SuperVo { private: string a01; string a02; public: mapping(map<string,string>); equals(SuperVo*); …以下アクセサ } class SubBVo : public SuperVo { private: string b01; string b02; public: mapping(map<string,string>); equals(SuperVo*); …以下アクセサ } すべてのVOクラスを抱えるholderと適当なVoを 取得するClientを以下のように考えています。 class VoHolder { private: map<string,list<SuperVo*>> data; public: list<SuperVo*> Search(string name, SuperVo* condition){ // voクラスのequalsによって、該当するvoを検索する }; } class Client { execute(){ // 初期化は終了しているとして、 SubBVo* vo = new SubBVo(); vo.setB02("xx"); // 検索条件を登録 list<SuperVo*> voList = holder->Search("VO名", vo); // 1件と分かっている場合 list<SuperVo*>::iterator ite = voList.begin(); SubBVo* vo = dynamic_cast<SubBVo*>(*ite); cout << vo.getB01() << endl; // サブクラスのアクセサを使いたい }; } 今後、VOの種類が増えていくことが予想される状態で、 Searchの実装をしなくてすむようにVoクラスに継承関係 をもたせています(is-a関係のつもり…)。 その際、Client側でダウンキャストが不可避になってしまいますが、 設計が悪いということになりますか? 今後、VOクラスが増えた際にサブクラスのmappingとequalsメソッド のみを実装すれば、自由にVOを増やせていけるようなholderクラス をイメージしています。 (mappingについては、説明を省略しています) 現在C++で設計をしていますが、興味のある話題で、 Javaでも気になるところです。 オブジェクト指向の話題ということで、アドバイス等 ご教授頂ければと思っています。 (←オブジェクト指向になってないよ、とか…) よろしくお願いします。 | ||||||||||||||||||||||||
|
投稿日時: 2007-09-17 20:21
これだけじゃなんだかわからないので
なんともいえないですが。
SuperVoにもSearchの実装が無いですが。 中身が無いなら抽象なクラスにしてもいいし インタフェースでもいいし。
まず、ダウンキャストは悪いものではないです。 避けてもいいですが、避けなくてもそれだけで悪い実装と言えるわけではないです。 つぎに、これだけの情報では、 ダウンキャストは不可避であるかどうかはわかりません。 必要十分なメンバを基底クラスで定義すれば、 ダウンキャストが多発することはありません。 最後に。自由にできるものがいいとは限りません。 何でもできるクラス、汎用的なクラスは、大抵の場合、役に立ちません。無駄です。 最近の高級言語では、一般に役立つ汎用クラスは殆どクラスライブラリにあります。 Javaも.Netも実行時に型情報を取得できますし、 基底用のObjectがありますので、ToStringなども使えます。 これらを使えば、メンバが二つしかないSuperVOなど使わなくても十分です。 SuperVOに他になにもメンバがないなら、 汎用的過ぎて意味が無いコードです。 mappingが何をしているのかわかりませんので、 その部分に具体的な実装が入るとするなら、 その部分がなければなんともいえません。
以上のように、SuperVoが断片だとするなら、なんとも言えません。 断片で無いなら、無駄です。 気にしてる部分とかを考えると、 オブジェクト指向を使った実際の設計に慣れてない印象を受けます。 もともと、オブジェクト指向はこういったことをしたいから生まれたもので、 言語のサポートがない頃には、(いまでもしますが、) 構造体の先頭に構造体サイズを必ず入れることに決めたり、 IDやNameを特定の場所にいれ、それによってオブジェクトを区別したり。 プログラマがそれぞれ自分規約でオブジェクトを作ってました。 zrx62460さんのやってることはそれに近くありませんか? もう言語がサポートしてくれてるのに。 TCPパケットを自分TCPプロトコルで包んだり、 車で飛行機を運んだり、 貸金庫の中に鍵付き貯金箱をいれたりしては無駄です。 資源は節約しましょう。 | ||||||||||||||||||||||||
|
投稿日時: 2007-09-17 22:23
(C++ は良くは知りませんが。。。)
「設計が悪い」と思います。普通のアプリケーションだったら、自分が0から作るクラスで、ダウンキャストが必要になることはまずありません。 ダウンキャストが必要になるのは、なにか既存のクラスがあって、それをあまりいじらないで拡張したい場合など、やむを得ずおこなう場合だけだと考えたほうが良いです。 あるいは、自虐的ですが、クラス設計者にオブジェクト指向の知識が足りず、なまじ洗練されたたクラス設計にするよりはダウンキャストでやったほうが分かりやすい、という場合もあるかもしれません。 ただ、いずれにしてもダウンキャストはそうめったなことでは使うものではないものです。ダウンキャストが必要になった時点で「なにかがおかしい」といったんは考えるべきです。それでも上記のような理由で必要だと判断すれば、使っても良いかもしれませんが、でもやはりできるだけ避けたほうが良いでしょう。 -- unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86} | ||||||||||||||||||||||||
|
投稿日時: 2007-09-17 22:34
おそらく、デザインパターンでのコマンド(Command)に近いものだと思います。 コマンドだったら、お使いの mapping や equals のほかに、execute のようなメソッドもスーパータイプで備えておいて、どのクラスでも execute を呼ぶだけ、という共通化をおこなうことで対処します。提示されたような getB01 のようなローレベルなアクセッサーは隠蔽してしまい、外部には見せません。 ただ、これはかなり難しい作りになります。execute メソッドに共通化するのが結構面倒くさいです。この難しさを勘案して、前述のようにダウンキャストでやってしまう、というのもひとつのやりかたとしてはあります。 -- unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86} | ||||||||||||||||||||||||
|
投稿日時: 2007-09-18 02:31
れいさん
ご回答ありがとうございます。
Searchの実装自体はVoHolderに持っています。 VoHolder自身が抱えているデータから、Voで実装している equalsメソッドを呼び出し、該当するVoのlistを作成します。 実際、SuperVoには実装は必要なないので、抽象クラスで 良いかもしれません。
例では、メンバが2つしかないのですが、 実際には、もっとありますので、基底クラスで定義はできません。 今後VOが増えることを想定した場合、基底クラスの 修正を避けたいというのもあります。
確かにどこまで、汎用的にするかは悩ましいところですね。 今回のデータクラスの場合、Javaで言うところ(?)のORマッピング を意識しています。 実は、これらのデータクラスは外部リソースの情報を読込み データモデル化し、アプリ側で必要な情報を取り出したい というところから始まっています。 (具体的にはCSVファイル、後にDBとCSVの混在となる予定) 例えばJavaのseasar/s2daoなんかだと、VOにアノテーションの 定義をすれば、データモデル化してくれますよね。 今回の構成も ・holderクラスに外部読込みファイルとVOクラスのマッピング情報 ・voクラスにファイルのフィールドとVOクラスのメンバのマッピング情報 を持っています。 よって、今後のVO増加でロジックに近い部分はほぼ修正の必要が ないのが目標でした。
オブジェクト指向自体にはそれなりに自信が… ってこの投稿では言い訳になります(笑)。 業務モデルで使用するデータクラスであれば、このように ただ共通の型としてだけ使いたいような基底クラスは作らない のですが、今回のケースでは、業務から離れた部分ということも あり、より汎用的を目指しすぎかも。
とてもわかりやすいご指摘です。 ありがとうございます。 資源の節約は私も同感なので、もうちょっと考えてみます。 ※説明不足ですいません。すべてのコードを載せる訳にもいかないので難しいですね。 | ||||||||||||||||||||||||
|
投稿日時: 2007-09-18 02:48
unibonさん
ご回答ありがとうございます。
同感です。C++は今回が初めてなのですが、これまで、あまりキャストを意識して いませんでした。きっとJavaでも同様の問題はあったと思うのですが…。
ClientからするとgetB01のようなアクセサを使いたいというのが一番になります。 妥協点を考えはじめると、 ダウンキャストは避けたいので、わざわざデータモデル化しなくてもいいような気がしますし。 mapやlistなどコレクションクラスをうまく使えば、VOを作らなくてもできるんですよね。 もうちょっとだけ、悩んでみます。 | ||||||||||||||||||||||||
|
投稿日時: 2007-09-18 15:31
普通objectにはequalsやそれに類するメソッド(hashcode、tostringなど)があります。 検索にはそれを使えますので、その目的のためにSuperVOを作るのは無駄です。 共通のメンバを定義したいのであればSuperVOの意味があります。
「メンバがたくさんあるから基底クラスで定義できない」 というのは論理がよく理解できません。 派生内容によって違うので定義できないというのであればわかります。 そして、メンバが一つも定義できないのであれば、 上で書いたようにSuperVOには意味がありません。 その為にobject.equalsがあります。
SuperVOにいろいろメンバを持っているなら、 もちろん意味があります。
SuperVOにメンバがあるなら、この手のはよくやりますので、 慣れてる人ならいちいち質問するまでもないのではないかと私は思います。 (その認識が間違っている可能性も少なくないですが。) SuperVOにメンバが無いなら、無駄です。 つまり、慣れていないと判断しましたが。 私はオブジェクト指向の専門家ではないので、 自信があるならば言うことはありません。
unibonさんはこう言っていますが、私は結構使います。 .Net1.1のころはジェネリックも無いので、コレクションではよく使いましたし。 キャスト可能か調べなきゃいけないので、もちろん好きではないですが、 必要とあれば躊躇なく使います。 [ メッセージ編集済み 編集者: れい 編集日時 2007-09-18 16:27 ] | ||||||||||||||||||||||||
|
投稿日時: 2007-09-18 16:57
提示されたソースコードを見ただけで判断するなら、unibonさんの
が良さそうですね。 「ClientからするとgetB01のようなアクセサを使いたい」のはなぜでしょうか。getB01で値を取得するのは、その値を使って「何か」をするためですよね。 「何か」をする、というメソッドを作ることはできませんか?(unibonさんの言うexecute) そして、それをSuperVoのメンバにすることはできませんか?(純粋仮想関数でも構いません) もしそれができない、つまりSubBVoとして独特の呼び出し(処理ではなく呼び出し)をする必要があり、holder->Search()で取得するインスタンスの型はSubBVoであるとClient側が知っている必要があるならzrx62460さんの提示されたソースでいいんじゃないでしょうか。つまりダウンキャストをするということです。 あるいはSuperVoのコレクションの他にSubBVoだけのコレクションを作るという手もありますね。 SubBVoが要求されている処理なんですから、わざわざSuperVoのコレクションから取って来る必要はないですよね。 |