- PR -

インターフェイスによる継承と抽象化クラスによる継承の使い分け

投稿者投稿内容
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-09-05 11:11
ここまでを流し読みしかしていませんが…。

そもそもインターフェースを必要とするクライアントがいなければ、いくらインターフェースを実装・公開しても全く意味がないのでは。

なので、まず「最初に有りき」なのが「クライアント」ですよね? 「is-a」だとか「〜ができる」だとかが最初の動機になる事はないはずです。

あるクライアントが他のクラスを使いたいんだけど、実装の詳細が分からない(或いは知りたくない)。だけど、「〜できればとりあえず良い」という事でインターフェースだけを必要とする(ある程度実装を必要とするなら抽象クラスを必要とすれば良い)。

で、そのクライアントに使って欲しいと思うクラスを書くならインターフェースを実装する以外に道はないわけで。インターフェースを実装する動機って普通こうではないですか?

クライアントもいないのにインターフェースを実装するのか抽象クラスを継承するのか悩むのは違うかなぁと思います。

_________________
囚人のジレンマな日々
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-09-05 11:46
引用:

囚人さんの書き込み (2007-09-05 11:11) より:
そもそもインターフェースを必要とするクライアントがいなければ、いくらインターフェースを実装・公開しても全く意味がないのでは。



すいません。素朴な疑問なんですが、ここでいうクライアントってなんですか?
具象クラスのこと?他の開発者のこと?お客さんのこと?あるいはそれ以外ですか?
文脈からだけでは何のことを「クライアント」とおっしゃっているのかよくわかりませんでしたので、教えてください。
囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-09-05 11:50
引用:

すいません。素朴な疑問なんですが、ここでいうクライアントってなんですか?
具象クラスのこと?他の開発者のこと?お客さんのこと?あるいはそれ以外ですか?
文脈からだけでは何のことを「クライアント」とおっしゃっているのかよくわかりませんでしたので、教えてください。


失礼しました。

「具象クラスのこと」です。具象でなくても、抽象でも他のインターフェースでも良いですが。
プログラミング言語の場合もあります。例えば C# の foreach とか。
_________________
囚人のジレンマな日々
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-09-05 13:53
「クライアント」の意味付けについて、ご返答ありがとうございます。

引用:

囚人さんの書き込み (2007-09-05 11:50) より:
「具象クラスのこと」です。具象でなくても、抽象でも他のインターフェースでも良いですが。



具象クラスだけでなく、サブクラスとしての抽象クラス、インタフェースも含む
ということですね。

引用:

プログラミング言語の場合もあります。例えば C# の foreach とか。



さらにいうと単にサブクラスだけを意図するわけでなく、
そのクラスを使う場面・用途といったニュアンスも含んでいるということでしょうか。
foreachのためにIEnumerableを用意したように。

引用:

囚人さんの書き込み (2007-09-05 11:11) より:
クライアントもいないのにインターフェースを実装するのか抽象クラスを継承するのか悩むのは違うかなぁと思います。



つまり、「どのように使うのかを想定せずに」インターフェースを実装するのか抽象クラスを継承するのか判断できないのでは?という理解であってますか?

#+アルファの情報提供がなくてすみません。ちょっと気になったもので>ALL

囚人
ぬし
会議室デビュー日: 2005/08/13
投稿数: 1019
投稿日時: 2007-09-05 14:18
引用:

具象クラスだけでなく、サブクラスとしての抽象クラス、インタフェースも含む
ということですね。


私の説明が足りなくて誤解があったかもしれません。

コード:
class Client
{
	void Do(IHogetable hogetable){}
}


Client クラスは IHogetable インターフェースのクライアントです。


引用:

さらにいうと単にサブクラスだけを意図するわけでなく、
そのクラスを使う場面・用途といったニュアンスも含んでいるということでしょうか。
foreachのためにIEnumerableを用意したように。


私が言いたいのは、サブクラスを最初に考慮してはだめで、そのインターフェースを使う場面を最初に考慮すべきという事です。


引用:

つまり、「どのように使うのかを想定せずに」インターフェースを実装するのか抽象クラスを継承するのか判断できないのでは?という理解であってますか?


はい。その通りです。インターフェースは特にその意味が強いと思います。抽象クラスはその限りではないかなと思いますが。

_________________
囚人のジレンマな日々
ぼのぼの
ぬし
会議室デビュー日: 2004/09/16
投稿数: 544
投稿日時: 2007-09-06 01:22
引用:

れいさんの書き込み (2007-09-05 05:42) より:

私ならどうするかとか、
私は「こうするべき」と思ってるとか、
そういったことしか答えられませんが。


十分です。おおいに参考になります。

引用:

コード:
abstract TableParent {
 Read(), Edit(), Add(), Delete(), Search(),
 IsReadable, IsEditable, IsAddable, IsDeletable, IsSearchable,
 ...
}

OneTable : TableParent {
 Read(), Edit(), Add(), Delete(), Search(),
 IsReadable, IsEditable, IsAddable, IsDeletable, IsSearchable,
 ...
}



DBのテーブルのラッパの場合、
私なら後者で作ります。

普段は追加不可能でも、動的に追加可能になったりする場合に対応できますし。
パフォーマンスも一番いいですし、
オブジェクト数が少なくて済みますし、
操作が可能かどうかはIsXxxableを見ればいいだけですから。


なるほど。

将来の拡張の可能性への配慮、すなわち柔軟性を考えるとそうなっちゃいますよね。

しかし、現時点の仕様だけを見ると、あるテーブルに対して削除はないので、
「Delete()がコールされた」としたら、これはコーディングミスである可能性が高い。
IsDeletableを見るまでもなく、Delete()自体コールされる機会は無い筈。

構造上はIsDeletableを無視して、Delete()をコールすること自体は可能なわけで。

このミスをコンパイルの段階で発見できるのが良いのか、
動かしてから例外という形でわかるのが良いのか、
それともミスを吸収してしまう(コールされても何もしない)のが良いのか。
この「コーディングミスへの配慮」と「拡張の可能性への配慮」のトレードオフが
この問題の肝だと思っています。

結局ケースバイケースってことになっちゃうのかもしれませんが、
DBのテーブルのラッパなんてのは結構良くでてくる話だと思うので、
皆さんどうされてるのかなぁと思った次第です


追記:
>IsDeletableを見るまでもなく、Delete()自体コールされる機会は無い筈。
よく考えたら使う側の設計次第では、ありますね。
View側も継承なり委譲なりで一部共通化していた場合。
例えば、削除ボタンのイベントハンドラは実装しちゃってあって、
ボタンのVisibleをIsDeletableとひっつけとくとかできるわけだ。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2007-09-06 02:30
引用:

ぼのぼのさんの書き込み (2007-09-06 01:22) より:
このミスをコンパイルの段階で発見できるのが良いのか、
動かしてから例外という形でわかるのが良いのか、
それともミスを吸収してしまう(コールされても何もしない)のが良いのか。
この「コーディングミスへの配慮」と「拡張の可能性への配慮」のトレードオフが
この問題の肝だと思っています。



最後の「吸収する」のは、「実行できるがなにもしない」ということですので、
「エラーが起きても返さない」というのとは意味が異なりますよね。
「実行できるがなにもしない」というのが意味論的に正しい場合は例外を投げず、
間違っている場合には例外を投げるようにすべきだと私は思います。

私は「ミスをコンパイルの段階で発見できる」というのに
あまり重きを置かないようにするべきだと考えています。
語弊がありますが、IDEの支援機能程度と思うようにしようと。

これを考えすぎると、クラス数の爆発が起こり、
何も開発できなくなる人が多々現れるからです。
(私の環境のせいかもしれませんが。)

例えば。(仮想ライブラリです。Integerも仮。)
Integerを派生したNonZeroIntegerを作った人がいます。

NonZeroInteger Integer.Power( Integer value )
Double Integer.Divide( NonZeroInteger value )

となるそうです。
このクラスを使えば、/0エラーを発生させてしまう場合が減るに違いないと。
そして、次にNonNegativeInteger, NaturalNumberをつくり、

NonNegativeInteger NonNegativeInteger.Power( NonNegativeInteger value )
NonNegativeInteger Integer.Add( NonNegativeInteger value )
Integer NonNegativeInteger.Subtract( NonNegativeInteger value )
NaturalNumber NaturalNumber.Power( NaturalNumbervalue )


と、延々と実装していき、終わらなくなりました。

ほぼ同じ間違いを私の周りではかなり見ます。
あちこちで問題になるように、クラス分けの明確な基準が存在しません。
そのため、勘の働かない人が作ると、クラスを分け続けてしまうのです。

継承が無くてもまともに動くプログラムは作れます。
継承を考えすぎて作れなくなるくらいなら、
なるべく使わないようにして、
使ったらうまく行きそうな所が見えるようになるまで経験を積むほうがいいと。

old-styleな職人のような意見で申し訳ないですが。
これに関しては習うより慣れろ。かな。
他の人の意見を聞きたいですね。
これを読めば継承で失敗しない!みたいな文章があるといいんですが。

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