- PR -

データクラスの設計とダウンキャストの回避について

投稿者投稿内容
zrx62460
会議室デビュー日: 2007/09/17
投稿数: 5
投稿日時: 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でも気になるところです。
オブジェクト指向の話題ということで、アドバイス等
ご教授頂ければと思っています。
(←オブジェクト指向になってないよ、とか…)
よろしくお願いします。

れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-09-17 20:21
これだけじゃなんだかわからないので
なんともいえないですが。

引用:

今後、VOの種類が増えていくことが予想される状態で、
Searchの実装をしなくてすむようにVoクラスに継承関係
をもたせています(is-a関係のつもり…)。



SuperVoにもSearchの実装が無いですが。
中身が無いなら抽象なクラスにしてもいいし
インタフェースでもいいし。

引用:

その際、Client側でダウンキャストが不可避になってしまいますが、
設計が悪いということになりますか?
今後、VOクラスが増えた際にサブクラスのmappingとequalsメソッド
のみを実装すれば、自由にVOを増やせていけるようなholderクラス
をイメージしています。



まず、ダウンキャストは悪いものではないです。
避けてもいいですが、避けなくてもそれだけで悪い実装と言えるわけではないです。

つぎに、これだけの情報では、
ダウンキャストは不可避であるかどうかはわかりません。
必要十分なメンバを基底クラスで定義すれば、
ダウンキャストが多発することはありません。

最後に。自由にできるものがいいとは限りません。
何でもできるクラス、汎用的なクラスは、大抵の場合、役に立ちません。無駄です。
最近の高級言語では、一般に役立つ汎用クラスは殆どクラスライブラリにあります。

Javaも.Netも実行時に型情報を取得できますし、
基底用のObjectがありますので、ToStringなども使えます。
これらを使えば、メンバが二つしかないSuperVOなど使わなくても十分です。
SuperVOに他になにもメンバがないなら、
汎用的過ぎて意味が無いコードです。

mappingが何をしているのかわかりませんので、
その部分に具体的な実装が入るとするなら、
その部分がなければなんともいえません。

引用:

オブジェクト指向の話題ということで、アドバイス等
ご教授頂ければと思っています。
(←オブジェクト指向になってないよ、とか…)



以上のように、SuperVoが断片だとするなら、なんとも言えません。
断片で無いなら、無駄です。
気にしてる部分とかを考えると、
オブジェクト指向を使った実際の設計に慣れてない印象を受けます。

もともと、オブジェクト指向はこういったことをしたいから生まれたもので、
言語のサポートがない頃には、(いまでもしますが、)
構造体の先頭に構造体サイズを必ず入れることに決めたり、
IDやNameを特定の場所にいれ、それによってオブジェクトを区別したり。
プログラマがそれぞれ自分規約でオブジェクトを作ってました。

zrx62460さんのやってることはそれに近くありませんか?
もう言語がサポートしてくれてるのに。

TCPパケットを自分TCPプロトコルで包んだり、
車で飛行機を運んだり、
貸金庫の中に鍵付き貯金箱をいれたりしては無駄です。

資源は節約しましょう。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-09-17 22:23
(C++ は良くは知りませんが。。。)

引用:

zrx62460さんの書き込み (2007-09-17 14:10) より:
その際、Client側でダウンキャストが不可避になってしまいますが、
設計が悪いということになりますか?


「設計が悪い」と思います。普通のアプリケーションだったら、自分が0から作るクラスで、ダウンキャストが必要になることはまずありません。

ダウンキャストが必要になるのは、なにか既存のクラスがあって、それをあまりいじらないで拡張したい場合など、やむを得ずおこなう場合だけだと考えたほうが良いです。
あるいは、自虐的ですが、クラス設計者にオブジェクト指向の知識が足りず、なまじ洗練されたたクラス設計にするよりはダウンキャストでやったほうが分かりやすい、という場合もあるかもしれません。
ただ、いずれにしてもダウンキャストはそうめったなことでは使うものではないものです。ダウンキャストが必要になった時点で「なにかがおかしい」といったんは考えるべきです。それでも上記のような理由で必要だと判断すれば、使っても良いかもしれませんが、でもやはりできるだけ避けたほうが良いでしょう。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2007-09-17 22:34
引用:

zrx62460さんの書き込み (2007-09-17 14:10) より:
今後、VOの種類が増えていくことが予想される状態で、
Searchの実装をしなくてすむようにVoクラスに継承関係
をもたせています(is-a関係のつもり…)。



引用:

zrx62460さんの書き込み (2007-09-17 14:10) より:
今後、VOクラスが増えた際にサブクラスのmappingとequalsメソッド
のみを実装すれば、自由にVOを増やせていけるようなholderクラス
をイメージしています。
(mappingについては、説明を省略しています)


おそらく、デザインパターンでのコマンド(Command)に近いものだと思います。
コマンドだったら、お使いの mapping や equals のほかに、execute のようなメソッドもスーパータイプで備えておいて、どのクラスでも execute を呼ぶだけ、という共通化をおこなうことで対処します。提示されたような getB01 のようなローレベルなアクセッサーは隠蔽してしまい、外部には見せません。
ただ、これはかなり難しい作りになります。execute メソッドに共通化するのが結構面倒くさいです。この難しさを勘案して、前述のようにダウンキャストでやってしまう、というのもひとつのやりかたとしてはあります。

--
unibon {B73D0144-CD2A-11DA-8E06-0050DA15BC86}
zrx62460
会議室デビュー日: 2007/09/17
投稿数: 5
投稿日時: 2007-09-18 02:31
れいさん
ご回答ありがとうございます。

引用:

SuperVoにもSearchの実装が無いですが。
中身が無いなら抽象なクラスにしてもいいし
インタフェースでもいいし。



Searchの実装自体はVoHolderに持っています。
VoHolder自身が抱えているデータから、Voで実装している
equalsメソッドを呼び出し、該当するVoのlistを作成します。

実際、SuperVoには実装は必要なないので、抽象クラスで
良いかもしれません。

引用:

つぎに、これだけの情報では、
ダウンキャストは不可避であるかどうかはわかりません。
必要十分なメンバを基底クラスで定義すれば、
ダウンキャストが多発することはありません。



例では、メンバが2つしかないのですが、
実際には、もっとありますので、基底クラスで定義はできません。
今後VOが増えることを想定した場合、基底クラスの
修正を避けたいというのもあります。

引用:

最後に。自由にできるものがいいとは限りません。
何でもできるクラス、汎用的なクラスは、大抵の場合、役に立ちません。無駄です。
最近の高級言語では、一般に役立つ汎用クラスは殆どクラスライブラリにあります。

Javaも.Netも実行時に型情報を取得できますし、
基底用のObjectがありますので、ToStringなども使えます。
これらを使えば、メンバが二つしかないSuperVOなど使わなくても十分です。
SuperVOに他になにもメンバがないなら、
汎用的過ぎて意味が無いコードです。



確かにどこまで、汎用的にするかは悩ましいところですね。
今回のデータクラスの場合、Javaで言うところ(?)のORマッピング
を意識しています。
実は、これらのデータクラスは外部リソースの情報を読込み
データモデル化し、アプリ側で必要な情報を取り出したい
というところから始まっています。
(具体的にはCSVファイル、後にDBとCSVの混在となる予定)

例えばJavaのseasar/s2daoなんかだと、VOにアノテーションの
定義をすれば、データモデル化してくれますよね。
今回の構成も
・holderクラスに外部読込みファイルとVOクラスのマッピング情報
・voクラスにファイルのフィールドとVOクラスのメンバのマッピング情報
を持っています。
よって、今後のVO増加でロジックに近い部分はほぼ修正の必要が
ないのが目標でした。

引用:

以上のように、SuperVoが断片だとするなら、なんとも言えません。
断片で無いなら、無駄です。
気にしてる部分とかを考えると、
オブジェクト指向を使った実際の設計に慣れてない印象を受けます。



オブジェクト指向自体にはそれなりに自信が…
ってこの投稿では言い訳になります(笑)。
業務モデルで使用するデータクラスであれば、このように
ただ共通の型としてだけ使いたいような基底クラスは作らない
のですが、今回のケースでは、業務から離れた部分ということも
あり、より汎用的を目指しすぎかも。

引用:

zrx62460さんのやってることはそれに近くありませんか?
もう言語がサポートしてくれてるのに。

TCPパケットを自分TCPプロトコルで包んだり、
車で飛行機を運んだり、
貸金庫の中に鍵付き貯金箱をいれたりしては無駄です。

資源は節約しましょう。



とてもわかりやすいご指摘です。
ありがとうございます。

資源の節約は私も同感なので、もうちょっと考えてみます。

※説明不足ですいません。すべてのコードを載せる訳にもいかないので難しいですね。
zrx62460
会議室デビュー日: 2007/09/17
投稿数: 5
投稿日時: 2007-09-18 02:48
unibonさん
ご回答ありがとうございます。

引用:

「設計が悪い」と思います。普通のアプリケーションだったら、自分が0から作るクラスで、ダウンキャストが必要になることはまずありません。

(省略)ダウンキャストが必要になった時点で「なにかがおかしい」といったんは考えるべきです。それでも上記のような理由で必要だと判断すれば、使っても良いかもしれませんが、でもやはりできるだけ避けたほうが良いでしょう。



同感です。C++は今回が初めてなのですが、これまで、あまりキャストを意識して
いませんでした。きっとJavaでも同様の問題はあったと思うのですが…。

引用:

おそらく、デザインパターンでのコマンド(Command)に近いものだと思います。
コマンドだったら、お使いの mapping や equals のほかに、execute のようなメソッドもスーパータイプで備えておいて、どのクラスでも execute を呼ぶだけ、という共通化をおこなうことで対処します。提示されたような getB01 のようなローレベルなアクセッサーは隠蔽してしまい、外部には見せません。



ClientからするとgetB01のようなアクセサを使いたいというのが一番になります。

妥協点を考えはじめると、
ダウンキャストは避けたいので、わざわざデータモデル化しなくてもいいような気がしますし。
mapやlistなどコレクションクラスをうまく使えば、VOを作らなくてもできるんですよね。

もうちょっとだけ、悩んでみます。

れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-09-18 15:31
引用:

zrx62460さんの書き込み (2007-09-18 02:31) より:
Searchの実装自体はVoHolderに持っています。
VoHolder自身が抱えているデータから、Voで実装している
equalsメソッドを呼び出し、該当するVoのlistを作成します。



普通objectにはequalsやそれに類するメソッド(hashcode、tostringなど)があります。
検索にはそれを使えますので、その目的のためにSuperVOを作るのは無駄です。
共通のメンバを定義したいのであればSuperVOの意味があります。

引用:

例では、メンバが2つしかないのですが、
実際には、もっとありますので、基底クラスで定義はできません。



「メンバがたくさんあるから基底クラスで定義できない」
というのは論理がよく理解できません。
派生内容によって違うので定義できないというのであればわかります。
そして、メンバが一つも定義できないのであれば、
上で書いたようにSuperVOには意味がありません。
その為にobject.equalsがあります。

引用:

今回の構成も
・holderクラスに外部読込みファイルとVOクラスのマッピング情報
・voクラスにファイルのフィールドとVOクラスのメンバのマッピング情報
を持っています。



SuperVOにいろいろメンバを持っているなら、
もちろん意味があります。

引用:

引用:

気にしてる部分とかを考えると、
オブジェクト指向を使った実際の設計に慣れてない印象を受けます。


オブジェクト指向自体にはそれなりに自信が…
ってこの投稿では言い訳になります(笑)。



SuperVOにメンバがあるなら、この手のはよくやりますので、
慣れてる人ならいちいち質問するまでもないのではないかと私は思います。
(その認識が間違っている可能性も少なくないですが。)
SuperVOにメンバが無いなら、無駄です。
つまり、慣れていないと判断しましたが。

私はオブジェクト指向の専門家ではないので、
自信があるならば言うことはありません。

引用:

unibonさんの書き込み (2007-09-17 22:23) より:
「設計が悪い」と思います。普通のアプリケーションだったら、自分が0から作るクラスで、ダウンキャストが必要になることはまずありません。



unibonさんはこう言っていますが、私は結構使います。
.Net1.1のころはジェネリックも無いので、コレクションではよく使いましたし。

キャスト可能か調べなきゃいけないので、もちろん好きではないですが、
必要とあれば躊躇なく使います。

[ メッセージ編集済み 編集者: れい 編集日時 2007-09-18 16:27 ]
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-09-18 16:57
提示されたソースコードを見ただけで判断するなら、unibonさんの
引用:

unibonさんの書き込み (2007-09-17 22:34) より:
お使いの mapping や equals のほかに、execute のようなメソッドもスーパータイプで備えておいて、どのクラスでも execute を呼ぶだけ、という共通化をおこなうことで対処します。提示されたような getB01 のようなローレベルなアクセッサーは隠蔽してしまい、外部には見せません。


が良さそうですね。
「ClientからするとgetB01のようなアクセサを使いたい」のはなぜでしょうか。getB01で値を取得するのは、その値を使って「何か」をするためですよね。
「何か」をする、というメソッドを作ることはできませんか?(unibonさんの言うexecute)
そして、それをSuperVoのメンバにすることはできませんか?(純粋仮想関数でも構いません)

もしそれができない、つまりSubBVoとして独特の呼び出し(処理ではなく呼び出し)をする必要があり、holder->Search()で取得するインスタンスの型はSubBVoであるとClient側が知っている必要があるならzrx62460さんの提示されたソースでいいんじゃないでしょうか。つまりダウンキャストをするということです。
あるいはSuperVoのコレクションの他にSubBVoだけのコレクションを作るという手もありますね。
SubBVoが要求されている処理なんですから、わざわざSuperVoのコレクションから取って来る必要はないですよね。

スキルアップ/キャリアアップ(JOB@IT)