私の質問は、スーパークラスの動物の特別なケースについてです。
Animal
はmoveForward()
とeat()
を使用できます。Seal
はAnimal
を拡張します。Dog
はAnimal
を拡張します。Animal
と呼ばれるHuman
を拡張する特別な生き物がいます。Human
はメソッドspeak()
も実装します(Animal
では実装されていません)。Animal
を受け入れる抽象メソッドの実装では、speak()
メソッドを使用したいと思います。ダウンキャストをしないとそれは不可能のようです。 Jeremy Millerは 彼の記事 に、ダウンキャストの匂いがすることを書いています。
この状況でダウンキャストを回避するための解決策は何ですか?
特定のクラスが何かをするためにHuman
型であるかどうかを知る必要があるメソッドがある場合、特に SOLIDの原則 を壊しています。
私の意見では、メソッドが特定のクラス型を期待している場合、それを特定のメソッドとして呼び出すには、そのメソッドを変更して、インターフェースではなく、そのクラスのみを受け入れるようにします。
このようなもの :
public void MakeItSpeak( Human obj );
そしてこれは好きではありません:
public void SpeakIfHuman( Animal obj );
問題は、あなたがダウンキャストしているということではなく、Human
にダウンキャストしているということです。代わりに、インターフェースを作成します。
public interface CanSpeak{
void speak();
}
public abstract class Animal{
//....
}
public class Human extends Animal implements CanSpeak{
public void speak(){
//....
}
}
public void mysteriousMethod(Animal animal){
//....
if(animal instanceof CanSpeak){
((CanSpeak)animal).speak();
}else{
//Throw exception or something
}
//....
}
このように、条件は動物がHuman
であるということではなく、話すことができるという条件です。これは、mysteriousMethod
がAnimal
を実装している限り、CanSpeak
の他の人間以外のサブクラスと連携できることを意味します。
動物に通信を追加できます。犬の鳴き声、人間が話す、シール..うーん..私はシールが何をしているのかわかりません。
しかし、あなたのメソッドはif(Animal is Human)Speak();に設計されているようです。
あなたが聞きたい質問は、代替案は何ですか?あなたが何を成し遂げたいのか正確にはわからないので、提案するのは難しいです。ダウンキャスト/アップキャストが最善のアプローチである理論的な状況があります。
この場合、AbstractAnimal
クラスのspeak()
のデフォルト実装は次のようになります。
_void speak() throws CantSpeakException {
throw new CantSpeakException();
}
_
その時点で、Abstractクラスにデフォルトの実装があり、正しく動作します。
_try {
thingy.speak();
} catch (CantSeakException e) {
System.out.println("You can't talk to the " + thingy.name());
}
_
はい、これはすべてのspeak
を処理するためにコード全体に試行錯誤が散在していることを意味しますが、これの代わりにif(thingy is Human)
がすべてのスポークをラップすることです。
例外の利点は、ある時点で話すことができる別の種類のものがあれば(オウム)、すべてのテストを再実装する必要がないことです。
時々ダウンキャストが必要かつ適切です。特に、何らかの能力を持つオブジェクトと持たないオブジェクトがあり、その能力のないオブジェクトをデフォルトの方法で処理しているときに、その能力が存在する場合にその能力を使用したい場合に適しています。簡単な例として、String
が他の任意のオブジェクトと等しいかどうかを尋ねられたとします。あるString
が別のString
と等しい場合、他の文字列の長さとバッキング文字配列を調べる必要があります。ただし、String
がDog
と等しいかどうかを尋ねられた場合、Dog
の長さにアクセスすることはできませんが、そうする必要はありません。代わりに、String
がそれ自体と比較することになっているオブジェクトがString
ではない場合、比較ではデフォルトの動作を使用する必要があります(他のオブジェクトが等しくないことを報告する)。
ダウンキャストが最も疑わしいと見なされるのは、キャストされるオブジェクトが適切なタイプであることが「わかっている」ときです。一般に、オブジェクトがCat
であることがわかっている場合は、Cat
型の変数ではなく、Animal
型の変数を使用して参照する必要があります。ただし、これが常に機能するとは限らない場合があります。たとえば、Zoo
コレクションは、偶数/奇数の配列スロットにオブジェクトのペアを保持する場合があります。各ペアのオブジェクトは、それらのオブジェクトに作用できない場合でも、相互に作用できることが期待されます。他のペア。そのような場合、各ペアのオブジェクトは、特定のパラメータタイプを受け入れる必要があるため、他のペアからオブジェクトを構文的にに渡すことができます。したがって、Cat
のplayWith(Animal other)
メソッドは、other
がCat
である場合にのみ機能する場合でも、Zoo
は次のことができる必要があります。 Animal[]
の要素を渡すため、そのパラメータタイプはAnimal
ではなくCat
である必要があります。
ダウンキャストが正当に避けられない場合は、無条件で使用する必要があります。重要な問題は、ダウンキャストを賢明に回避できる時期を決定し、合理的に可能な場合にそれを回避することです。
動物を受け入れる抽象メソッドの実装では、speak()メソッドを使用したいと思います。
いくつかの選択肢があります。
存在する場合は、リフレクションを使用してspeak
を呼び出します。利点:Human
に依存しません。短所:「speak」という名前への依存関係が隠されています。
新しいインターフェイスSpeaker
を導入し、インターフェイスにダウンキャストします。これは、特定のコンクリートタイプに依存するよりも柔軟性があります。 Human
を実装するためにSpeaker
を変更する必要があるという欠点があります。 Human
を変更できない場合、これは機能しません
Human
にダウンキャストします。これには、別のサブクラスに話させたいときにコードを変更する必要があるという欠点があります。理想的には、繰り返し戻って古いコードを変更することなく、コードを追加してアプリケーションを拡張する必要があります。