K.Maebashi's BBS

ご自由に書き込んでください。雑談も可。
テスト書き込みの類はテスト用掲示板にどうぞ

[日付順表示] [日付順インデックス] [スレッド順インデックス]

新規投稿 | 開設者ホームページへ戻る | ヘルプ

[740] 分析と設計
投稿者:774RR
2007/02/20 02:13:25

盛り上がっているのは良いのですが、なんだか飽きて^H^H^Hわからなくなってきたので、 長すぎるスレにつながずに新規発言してみるテスト。 こんな場合、皆様ならどのように対処なさるでしょうか?ご意見拝借。 ある案件Aにおいては、こーいう設計が適切だと分析したので、以下のように書いた。 struct bird { virtual void fly()=0; }; そして現実にうまく行った。 Q1.案件B(あるいはA’と言い換えても)では「こうもり」を扱う必要が生じた。 基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? 抽象論だけだと理解しきれないのは俺だけ?なんか具体的な例が欲しくなったのです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[741] Re:分析と設計
投稿者:(ぱ)
2007/02/20 02:13:25

>Q1.案件B(あるいはA’と言い換えても)では「こうもり」を扱う必要が生じた。 >基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? 鳥とかこうもりではあんまり具体的でもないと思うので、私は、例によって ドローツールっぽいものを考えるとします。 Shapeにdraw()を付けたら、その時はうまくいったとして。 Q1-1)機能拡張でShapeをグループ化できるようにしたとき、ShapeGroupクラスは  どこに位置づけるべきか? Q1-2)機能拡張で「補助線」機能を付けた。補助線は、描画中にだけ使用する機能で、  後工程に回らないからデータとしてファイルに保存しない。 A1-1(前橋版) ShapeGroupをShapeのサブクラスにして、draw()をオーバーライドしてその グループに含まれているShapeのdraw()メソッドを再帰的に呼んでやれば 描画はできますが、それはやっぱりアレなのでShapeのスーパークラスとして Visibleとかの抽象クラス(またはインタフェース)を作り、ShapeGroupとShapeは そのサブクラスかなあ。 たとえば「color」というフィールドは、ShapeGroupには要らないだろうし。 Shapeのグループ is a Shape と言って言えないことはないけど、苦しいし。 A1-2(前橋版) 補助線がShapeのコレクションに入らないとすれば、完全に別扱いかなあ。 もちろんVisibleを導入し、Visibleのコレクションを作って、Shapeは ShapeコレクションとVisibleコレクションの両方から参照される、という 構造でもいいけど、両方のコレクションをメンテするのも面倒だし。 >Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 具体例が思いつきませんが、「描画されないShape」ですよね。 void draw() {} でよさそうな… 以上、べたべたに実装よりの解答でした。
[この投稿を含むスレッドを表示] [この投稿を削除]
[742] Re:分析と設計
投稿者:タイガー
2007/02/20 02:13:25

面白そうなので勝手に考えてみました。 初期の設計として私なら以下のように考えます。 >Shapeにdraw()を付けたら、その時はうまくいったとして。 > >Q1-1)機能拡張でShapeをグループ化できるようにしたとき、ShapeGroupクラスは > どこに位置づけるべきか? > >Q1-2)機能拡張で「補助線」機能を付けた。補助線は、描画中にだけ使用する機能で、 > 後工程に回らないからデータとしてファイルに保存しない。 > >A1-1(前橋版) >ShapeGroupをShapeのサブクラスにして、draw()をオーバーライドしてその >グループに含まれているShapeのdraw()メソッドを再帰的に呼んでやれば >描画はできますが、それはやっぱりアレなのでShapeのスーパークラスとして >Visibleとかの抽象クラス(またはインタフェース)を作り、ShapeGroupとShapeは >そのサブクラスかなあ。 >たとえば「color」というフィールドは、ShapeGroupには要らないだろうし。 これは、私も(ぱ)さんと同様にします。 つまり、ShapeGroupとShapeを共にVisibleから継承させて、 ShapeGroupにVisibleのリストを持たせます。 そうすればCompositeパターンになり、描画するときは、 トップからつながってるShapeを全て描画できます。 >A1-2(前橋版) >補助線がShapeのコレクションに入らないとすれば、完全に別扱いかなあ。 >もちろんVisibleを導入し、Visibleのコレクションを作って、Shapeは >ShapeコレクションとVisibleコレクションの両方から参照される、という >構造でもいいけど、両方のコレクションをメンテするのも面倒だし。 Visibleのサブクラスとして補助線クラス(AuxLine)を追加します。 AuxLineには、Visibleを集約させて(持たせて)、実際はShapeクラスを 持ちます。必要な機能をShapeから委譲させます。 結局、新たなクラスをShapeのサブクラスにするのは、 結合度が強すぎてガチガチになるので、 ゆるやかな結合で設計しておいた方が良いのかと思います。 これだけの情報だと情報量が少ないですが、 もっとうまい設計があったら教えて欲しいです。 >>Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 > >具体例が思いつきませんが、「描画されないShape」ですよね。 > >void draw() {} ダチョウ、ペンギンを例外にするのは良くないので、 上記の実装なら同一扱いできるので良いと思います。 もしくは、間違ってfly()させちゃいけないのなら 例外を投げるのもありかなと思います。
[この投稿を含むスレッドを表示] [この投稿を削除]
[743] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

>ダチョウ、ペンギンを例外にするのは良くないので、 >上記の実装なら同一扱いできるので良いと思います。 そこまでは限りなく同意なのですが >もしくは、間違ってfly()させちゃいけないのなら >例外を投げるのもありかなと思います。 これは LSP 違反だと思うのです。 http://www2.ocn.ne.jp/~yamagu/object/LSP-J.pdf の Rectangle と Square の例[本当の問題]と同等な問題を内在させることになりそうです。 さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[745] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

んと、他人の意見だけ募集して自分の意見を出さないのはアレげなので私の見解。 LSP=同一基底クラスから派生したオブジェクトは、振る舞いが同一であると契約する 契約=派生クラス実装者と基底クラス利用者の間で取り決め ってことなので、そもそも struct bird { virtual void fly()=0; }; って設計した以上 bird 派生クラスは全て「飛べる」必要があり、飛べないものをこいつから派生したらダメ。 1.だから、この bird からダチョウクラスを派生させるのはダメ。 2.もしダチョウクラスを bird から派生させたいのであれば bird の実装を再検討。 ではどのような bird にすればよいか、は要件に応じて再分析が必要。 その再分析の際に「特定の設計原理(銀の弾丸)」ってものはありえない。 ただその分析の結果の実装は LSP に則るべし。 ってことなんですけど。
[この投稿を含むスレッドを表示] [この投稿を削除]
[746] Re:分析と設計
投稿者:のぐー
2007/02/20 02:13:25

>struct bird { virtual void fly()=0; }; オブジェクト指向ぜんぜん分かってない者ですがひとつだけ。 そもそも、birdって言う名前をつかうこと自体、おかしくないですか? 「こうもり」を派生したくなった時点で、名前をflying_animalとかに変更するのが筋では? そしたら「ダチョウ」を派生できないことは明らかなはずで。
[この投稿を含むスレッドを表示] [この投稿を削除]
[747] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

概ね、皆さんに同意ですね。 >struct bird { virtual void fly()=0; }; このクラス設計が意味するところは簡単で「鳥は飛べる」ただこれだけです。 論理学風の言い方をすれば「鳥ならば飛べる」。 >Q1.案件B(あるいはA’と言い換えても)では「こうもり」を扱う必要が生じた。 >基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? 「飛べるならば鳥である」ではありませんから、コウモリを鳥のサブクラスにすることはできません。 どうしても統一的に扱いたいのならば、Flying-Animal クラスでも導入すべきです。 >Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 >基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? これは、前提条件が崩れているわけですから、理想的には一からやり直すべきです。 鳥クラスの下に「飛べる鳥」クラスと「飛べない鳥」クラスを設けて、fly メソッドは飛べる鳥クラスに移動する…と、既存の bird.fly メソッドを使っているコードは動かなくなりますね。 コスト的にやり直しが許されないのならば、飛べない鳥クラスの fly メソッドは、何もしない空実装にするのも手かもしれません。
[この投稿を含むスレッドを表示] [この投稿を削除]
[748] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>bird 派生クラスは全て「飛べる」必要があり、飛べないものをこいつから派生したらダメ。 >1.だから、この bird からダチョウクラスを派生させるのはダメ。 >2.もしダチョウクラスを bird から派生させたいのであれば bird の実装を再検討。 >ではどのような bird にすればよいか、は要件に応じて再分析が必要。 >その再分析の際に「特定の設計原理(銀の弾丸)」ってものはありえない。 >ただその分析の結果の実装は LSP に則るべし。 鼻血が出るほど同意。
[この投稿を含むスレッドを表示] [この投稿を削除]
[749] Re:分析と設計
投稿者:タイガー
2007/02/20 02:13:25

>>もしくは、間違ってfly()させちゃいけないのなら >>例外を投げるのもありかなと思います。 >これは LSP 違反だと思うのです。 >http://www2.ocn.ne.jp/~yamagu/object/LSP-J.pdf >の Rectangle と Square の例[本当の問題]と同等な問題を内在させることになりそうです。 >さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。 ここで、前提条件として以下のものを考えます。 「struct bird { virtual void fly()=0; }; に対し、birdを継承したbat(こうもり)クラスの実装も クライアント側の処理もうまくいった。 ここで新たにpenguin(ペンギン)クラス(処理)を追加する必要が 生じた。」 この時の設計として、penguinクラスとbatクラスを 同一視(例えば、両方ともbirdから継承)すべきかどうかを考えてみます。 私の前回の発言のように、penguinクラスとbatクラスを 同一視して、penguinのfly()の実装で例外を投げようとすると、 birdを扱うクライアント側の処理でbatとpenguinを 同一扱いできないので、LSPに違反します。 つまり、penguinクラスのfly()の実装でどうしても例外を 投げたいのであれば、明らかに設計ミスで、birdクラスで 例外を投げるように設計しておかなければなりません。 しかし、penguinのfly()を、batクラスのfly()とクライアント側で 同一視できるように実装しておけば問題ありません (例えば、fly() {}のような空実装など)。 すなわち、774RRさんが、[745]の2でおっしゃってるように、 既に実装されているクライアント側の処理にマッチするように penguinのfly()を実装すれば問題ありません。 しかし、現実的には全てのクライアント側の処理を調べるのは 現実的ではないので、LSPを頼りに、 「penguinのfly()の振る舞いがbatのfly()の振る舞いと同等である」 という「常識的な」判断ができれば、クライアント側でbatとpenguinが 同一視できると判断しても妥当と言えるかもしれません。 また逆に、LSPに違反した継承を行った場合でも、 クライアント側で両方に共通した使い方のみで実装してあげれば 問題ありません (もちろん、その後、LSPに違反するような実装をクライアント側の 処理で行う実装を追加すれば問題が起きるので設計としては かなり危険な設計ですが)。 また、774RRさんの[743]での以下の疑問 >さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。 は、「既に存在するクライアント側の処理がどうなっているか」と 「penguinのfly()の実装をどうしたいか」の両方に依存しているので 常に正解の設計方法論はないと私は思います (クライアント側の処理をあらかじめ規定するには、Design by Contractとかに 類する機能が必要だと思います)。 ここで、私の出た結論としては、私自身かなり混乱しているのですが、 つまり、penguinクラスをbirdから派生させてbatと同一視すべきかどうか という設計を迫られたとき、これがLSPに当てはまるかどうかを 考えようとして、 「penguinのfly()の振る舞いが、batのfly()の振る舞いと同一か」 という疑問を持ったとき、 penguinのfly()を実装しようとした時に、どう転んでも クライアント側の実装がbatのfly()と同一視しかできない実装になっている のであれば、全く問題ない訳で、 クライアント側の実装により、少しでも同一視できない可能性があるなら そういう設計すべきでない訳で、 つまり、それをどう判断できるかと言うと、クライアント側のコードの 予想がつかないため、そんなに単純にLSPで判断できないような気が してきました(クラスが単純ならできる場合もありそうです)。 ここでは、fly()関数1つのみを考えている訳ですが、実際は、クラスは 複数の関数を持つ場合がほとんどなので組み合わせるともっと複雑になるし。 結局、同一視すべきかは、LSPはかなり参考にはなるが、 どんな局面でも通用する設計方法は存在せず、「実装依存」な気がします。
[この投稿を含むスレッドを表示] [この投稿を削除]
[751] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

>「こうもり」を派生したくなった時点で、名前をflying_animalとかに変更するのが筋では? 御意。それは大いにありだと思う今日子の頃です。 今回のこの案件A’A”では、それでうまく行きそうです。みな幸せ。 ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 struct fooable { virtual void do_foo()=0; }; struct barable { virtual void do_bar()=0; }; struct bazable { virtual void do_baz()=0; }; struct hoge : fooable, barable {...}; struct piyo : barable, bazable {...}; とてもまともに分析したとは思えない醜い設計です。 抽象度は増したのかもしれませんが、実用度が薄すぎます。まともに使いこなせるとはとてもとても。 # 自己反省をかねて age
[この投稿を含むスレッドを表示] [この投稿を削除]
[752] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 >struct fooable { virtual void do_foo()=0; }; >struct barable { virtual void do_bar()=0; }; >struct bazable { virtual void do_baz()=0; }; >struct hoge : fooable, barable {...}; >struct piyo : barable, bazable {...}; 推し進めるというか、どこかで一歩飛んでいる気がします。 FlyingAnimal は(あるいは、飛行機を統一的に扱おうとしてさらに上位に定義した FlyingObject でさえ)、IFlyable とは一線を画したものであるような気がします。 なぜそう思うのかは、いまのところ「直感」としか言いようがないのですが。
[この投稿を含むスレッドを表示] [この投稿を削除]
[753] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>>ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 >>struct fooable { virtual void do_foo()=0; }; >>struct barable { virtual void do_bar()=0; }; >>struct bazable { virtual void do_baz()=0; }; >>struct hoge : fooable, barable {...}; >>struct piyo : barable, bazable {...}; > >推し進めるというか、どこかで一歩飛んでいる気がします。 >FlyingAnimal は(あるいは、飛行機を統一的に扱おうとしてさらに上位に定義した FlyingObject でさえ)、IFlyable とは一線を画したものであるような気がします。 >なぜそう思うのかは、いまのところ「直感」としか言いようがないのですが。 失礼。 今回は既に bird からしてインターフェイスでしたね。 Java や C# が言うところの「インターフェイス」の意味づけが、自分の中でよくできていない感じです。
[この投稿を含むスレッドを表示] [この投稿を削除]