ダウンキャストを避ける方法


12

私の質問は、スーパークラスの動物の特別なケースについてです。

  1. 私のAnimalmoveForward()eat()
  2. Seal拡張しAnimalます。
  3. Dog拡張しAnimalます。
  4. そしてまた延びている特別な生き物がありますAnimalと呼ばれるがHuman
  5. Humanメソッドもspeak()実装しAnimalます(では実装されません)。

受け入れる抽象メソッドの実装では、メソッドAnimalを使用したいと思いspeak()ます。それはダウンキャストをしなければ不可能だと思われます。ジェレミー・ミラーは彼の記事で、ひどい臭いがすることを書いた。

この状況でダウンキャストを回避するための解決策は何ですか?


6
モデルを修正します。通常、既存の分類階層を使用してクラス階層をモデル化するのは間違った考えです。抽象化を作成するのではなく、既存のコードから抽象化を引き出してから、それにコードを適合させるようにしてください。
陶酔14年

2
動物に話したい場合、話す能力は何かを動物にするものの一部です。 artima.com/interfacedesign/PreferPoly.html
JeffO

8
moveForward?カニはどうですか?
ファビオマルコリーニ14年

効果的なJava、BTWではどのアイテム番号ですか?
goldilocks 14年

1
一部の人々は話すことができないので、あなたがしようとすると例外がスローされます。
TulainsCórdova

回答:


12

Human何かをするために特定のクラスが型であるかどうかを知る必要があるメソッドがある場合、特にいくつかのSOLID原則を破っています:

  • オープン/クローズド原則-将来、話すことができる新しい動物の種類(たとえば、オウム)を追加する必要がある場合、またはその種類に固有の何かをする必要がある場合、既存のコードを変更する必要があります
  • インターフェースの分離の原則-一般化しすぎているようです。動物は多種多様な種をカバーできます。

私の意見では、メソッドが特定のクラスタイプを予期している場合、その特定のメソッドを呼び出し、そのメソッドを変更して、インターフェイスではなくそのクラスのみを受け入れます。

このようなもの :

public void MakeItSpeak( Human obj );

そして、これは好きではありません:

public void SpeakIfHuman( Animal obj );

Animal呼び出されたときに抽象メソッドを作成することもできます。canSpeak具体的な実装では、「話す」ことができるかどうかを定義する必要があります。
ブランドン14年

しかし、私はあなたの答えが好きです。多くの複雑さは、何かをそうではない何かのように扱うことから生じます。
ブランドン14年

@Brandonその場合、「話す」ことができない場合、例外をスローします。または何もしません。
BЈовић

したがって、次のように過負荷にpublic void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}なります。public void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Bart Weber 14年

1
すべての方法-makeItSpeak(ISpeakingAnimal animal)-ISpeakingAnimalにAnimalを実装させることができます。makeItSpeak(Animal animal){ISpeakingAnimalのインスタンスの場合は話す}を使用することもできますが、かすかな匂いがします。
ptyx 14年

6

問題は、ダウンキャストしていることではなく、ダウンキャストしていること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、他の人間以外のサブクラスと連携できることを意味します。AnimalCanSpeak


その場合、動物の代わりにCanSpeakのインスタンスを引数として取る必要があります
ニュートピア14年

1
@Newtopianそのメソッドはから何も必要とせずAnimal、そのメソッドのすべてのユーザーが、CanSpeak型参照(またはHuman型参照)を介して送信したいオブジェクトを保持すると仮定します。その場合、そのメソッドはHumanそもそも使用できた可能性があり、導入する必要はありませんCanSpeak
イダンアリー14

インターフェイスを削除することは、メソッドシグネチャで特定であることにリンクされていません。「人間」のみが存在し、「CanSpeak」という人間のみが存在する場合にのみ、インターフェイスを削除できます。
ニュートピア14年

1
@Newtopianそもそも紹介CanSpeakした理由は、それを実装するもの(Human)ではなく、それを使用するもの(メソッド)があるからです。ポイントCanSpeakは、そのメソッドを具象クラスから分離することHumanです。物事をCanSpeak異なる方法で処理する方法がなければ、物事を区別する意味はありませんCanSpeak。私たちは...私たちはメソッドを持っているという理由だけでインターフェイスを作成していない
IDAN Arye

2

AnimalにCommunicateを追加できます。犬の鳴き声、人間が話す、アザラシ..えーと..アザラシが何をするのかわかりません。

ただし、メソッドはif(Animal is Human)Speak();

あなたが尋ねたいかもしれない質問は、代替案は何ですか?あなたが何を達成したいのか正確にはわからないので、提案するのは難しいです。理論的には、ダウンキャスト/アップキャストが最適なアプローチです。


15
アザラシはわからないわ、しかしきつねは未定義です。

2

この場合、クラスspeak()内のデフォルトの実装AbstractAnimalは次のようになります。

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());
}

はい、これは、everyを処理するためにコード全体に試行錯誤が散在していることを意味しますが、これにspeak代わる方法は、代わりにif(thingy is Human)すべてのtalkをラップすることです。

例外の利点は、ある時点で別の種類のことを話す(オウム)場合、すべてのテストを再実装する必要がないことです。


1
これは、例外を使用する正当な理由ではないと思います(Pythonコードでない限り)。
ブライアンチェン14年

1
これは技術的にはうまくいくので、私は反対票を投じませんが、この種のデザインは本当に好きではありません。親実装できない親レベルでメソッドを定義するのはなぜですか?オブジェクトの種類がわからない場合(および知っていれば、この問題は発生しません)、常にtry / catchを使用して、完全に回避可能な例外を確認する必要があります。canSpeak()メソッドを使用してこれをうまく処理することもできます。
ブランドン14年

2
私は、廃止された実装を使用しているため、例外をスローするために多数の作業メソッドを書き換えるという誰かの決定に起因する多くの欠陥を持つプロジェクトに取り組んできました。彼は実装を修正できたかもしれませんが、代わりに例外をどこにでも投げることを選びました。当然、何百ものバグを抱えてQAに入りました。そのため、呼び出されることになっていないメソッドで例外をスローすることに偏っています。それらが呼び出されることになっていない場合、それらを削除します
ブランドン14年

2
この場合、例外をスローする代わりに何もしないことがあります。結局のところ、彼らは:)話すことができない
BЈовић

@Brandonは、例外を除いて最初から設計することと、後で改良することとで違いがあります。また、Java8のデフォルトのメソッドを使用したインターフェース、または例外をスローせずに単に黙っているインターフェースを見ることができます。ただし、重要な点は、ダウンキャストする必要を避けるために、渡される型で関数を定義する必要があることです。

1

ダウンキャストが必要で適切な場合もあります。特に、何らかの能力を持っているか持っていないオブジェクトがあり、その能力のないオブジェクトをデフォルトの方法で処理している間にその能力を使用したい場合に適しています。簡単な例として、a Stringが他の任意のオブジェクトと等しいかどうかを尋ねられたとします。一方が他方Stringと等しくなるにStringは、もう一方の文字列の長さとバッキング文字配列を調べる必要があります。ただし、Stringaに等しいかどうかを尋ねられた場合、Dogの長さにアクセスすることはできませんDogが、そうする必要はありません。代わりに、a Stringがそれ自体を比較することになっているオブジェクトがString、比較ではデフォルトの動作を使用する必要があります(他のオブジェクトが等しくないことを報告します)。

ダウンキャストが最も疑わしいと見なされるべき時は、キャストされるオブジェクトが適切なタイプであることが「わかっている」ときです。一般に、オブジェクトがa Catであることがわかっている場合、それを参照するにはCat、typeの変数ではなく、typeの変数を使用する必要がありますAnimal。ただし、これが常に機能するとは限りません。たとえば、Zooコレクションは、偶数/奇数配列スロットにオブジェクトのペアを保持し、各ペアのオブジェクトが他のペアのオブジェクトに作用できない場合でも、互いに作用できることが期待されます。そのような場合、各ペアのオブジェクトは、構文上、他のペアからオブジェクトを渡すことができるように、非特定のパラメータータイプを受け入れる必要があります。したがって、たとえCatさんplayWith(Animal other)この方法は、ときにのみ動作しますotherCatZooそれをの要素を渡すことができる必要があるだろうAnimal[]そのパラメータの型がでなければならないので、AnimalではなくCat

ダウンキャスティングが合法的に避けられない場合、問題なく使用する必要があります。重要な問題は、いつダウンキャストを適切に回避できるかを判断し、適切に可能な場合は回避することです。


文字列の場合、むしろmethodが必要Object.equalToString(String string)です。boolean String.equal(Object object) { return object.equalStoString(this); }そうすれば、ダウンキャストは不要です。動的ディスパッチを使用できます。
ジョルジオ14年

@Giorgio:動的ディスパッチには用途がありますが、一般的にダウンキャストよりもさらにひどいです。
supercat

動的なディスパッチングは一般的にダウンキャスティングよりもさらに悪い方法ですか?私はそれは別の方法です
ブライアンチェン14年

@BryanChen:それはあなたの用語に依存します。仮想メソッドはないと思いますObjectequalStoString、引用された例がJavaでどのように機能するかわからないことは認めますが、C#では、動的ディスパッチ(仮想ディスパッチとは異なる)は、コンパイラが本質的に持っていることを意味しますクラスで最初にメソッドが使用されるときにリフレクションベースの名前検索を行うには、仮想ディスパッチ(有効なメソッドアドレスを含む必要がある仮想メソッドテーブルのスロットを介して単に呼び出しを行う)とは異なります。
supercat

モデリングの観点から、一般的には動的ディスパッチを好むでしょう。また、入力パラメータのタイプに基づいてプロシージャを選択するオブジェクト指向の方法でもあります。
ジョルジオ14年

1

動物を受け入れる抽象メソッドの実装では、speak()メソッドを使用します。

いくつかの選択肢があります。

  • リフレクションを使用してspeak、存在する場合に呼び出します。利点:に依存しませんHuman。欠点:名前 "speak"に隠れた依存関係が存在するようになりました。

  • 新しいインターフェースSpeakerを導入し、インターフェースにダウンキャストします。これは、特定の具体的なタイプに依存するよりも柔軟です。Human実装するために変更する必要があるという欠点がありますSpeaker。変更できない場合、これは機能しませんHuman

  • へのダウンキャストHuman。これには、別のサブクラスに話したいときはいつでもコードを変更する必要があるという欠点があります。理想的には、古いコードを繰り返し変更することなく、コードを追加してアプリケーションを拡張したいと考えています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.