171

Java の多重継承の問題を解決する方法を完全に理解するために、明確にする必要がある古典的な質問があります。

私がクラスを持っているとしましょう。Animalこれにはサブクラスがあり、鳥と馬の両方から拡張されたクラスBirdHorse作成する必要があります。PegasusBirdHorsePegasus

これは古典的なダイヤモンドの問題だと思います。私が理解できることから、これを解決するための古典的な方法は、、AnimalおよびBirdクラスHorseのインターフェースを作成し、Pegasusそれらから実装することです。

鳥や馬のオブジェクトをまだ作成できるという問題を解決する別の方法があるかどうか疑問に思っていました。動物を作成できる方法があれば、それは素晴らしいことですが、必須ではありません。

4

17 に答える 17

118

public interface Equidae馬や鳥などの動物クラス (生物学的な意味でのクラス) のインターフェイスを作成できますpublic interface Avialae(私は生物学者ではないため、用語が間違っている可能性があります)。

その後、引き続き作成できます

public class Bird implements Avialae {
}

public class Horse implements Equidae {}

そしてまた

public class Pegasus implements Avialae, Equidae {}

コメントから追加:

重複コードを減らすために、実装したい動物の共通コードのほとんどを含む抽象クラスを作成できます。

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

アップデート

もう1つ詳細を追加したいと思います。Brian が述べているように、これは OP が既に知っていたことです。

ただし、インターフェイスで「多重継承」の問題を回避することを提案し、すでに具体的な型 (Bird など) を表すインターフェイスを使用することはお勧めしませんが、より多くの動作を表すインターフェイスを使用することはお勧めしません (他の人はアヒルのタイピングも良いですが、私が言いたいのは、鳥の生物学的分類である鳥類です)。また、大文字の「I」で始まるインターフェイス名を使用することもお勧めしません。たとえばIBird、インターフェイスが必要な理由がわかりません。それが問題の違いです。インターフェイスを使用して継承階層を構築し、必要に応じて抽象クラスを使用し、必要に応じて具象クラスを実装し、必要に応じて委任を使用します。

于 2014-02-17T08:52:09.410 に答える
88

オブジェクトを結合するには、次の 2 つの基本的な方法があります。

  • 1 つ目は継承です。すでに継承の制限を特定しているため、ここで必要なことを行うことはできません。
  • 2 つ目はコンポジションです。継承が失敗したため、構成を使用する必要があります。

これが機能する方法は、Animal オブジェクトがあることです。そのオブジェクト内に、必要なプロパティと動作を提供するオブジェクトをさらに追加します。

例えば:

  • BirdAnimal実装の IFlierを拡張します
  • 動物の道具を伸ばす草食動物、四足動物
  • ペガサスは、動物の道具IHerbivore、IQuadruped、IFlier を拡張します

IFlier次のようになります。

 interface IFlier {
     Flier getFlier();
 }

したがって、Bird次のようになります。

 class Bird extends Animal implements IFlier {
      Flier flier = new Flier();
      public Flier getFlier() { return flier; }
 }

これで、継承のすべての利点が得られます。コードを再利用できます。IFliers のコレクションを持つことができ、ポリモーフィズムなどの他のすべての利点を使用できます。

ただし、コンポジションのすべての柔軟性も備えています。各タイプに好きなだけ多くの異なるインターフェイスと複合バッキング クラスを適用できますAnimal。各ビットの設定方法を必要なだけ制御できます。

構成への戦略パターン代替アプローチ

何をどのように行っているかに応じた代替アプローチは、Animal基本クラスに内部コレクションを含めて、さまざまな動作のリストを保持することです。その場合、戦略パターンに近いものを使用することになります。これは、コードを簡素化するという点で利点があります (たとえば、またはHorseについて何も知る必要はありません) が、インターフェイス アプローチも行わないと、ポリモーフィズムなどの多くの利点が失われます。QuadrupedHerbivore

于 2014-02-17T09:35:19.423 に答える
45

私はばかげた考えを持っています:

public class Pegasus {
    private Horse horseFeatures; 
    private Bird birdFeatures; 

   public Pegasus(Horse horse, Bird bird) {
     this.horseFeatures = horse;
     this.birdFeatures = bird;
   }

  public void jump() {
    horseFeatures.jump();
  }

  public void fly() {
    birdFeatures.fly();
  }
}
于 2014-02-17T09:09:51.470 に答える
26

ダックタイピングの概念を提案してもよろしいですか?

ほとんどの場合、ペガサスに鳥と馬のインターフェースを拡張させる傾向がありますが、ダックタイピングは実際には動作を継承する必要があることを示唆しています。コメントですでに述べたように、ペガサスは鳥ではありませんが、飛ぶことができます。したがって、ペガサスはむしろインターフェースを継承する必要があり、Flyableインターフェースと言えますGallopable

この種の概念は、戦略パターンで利用されます。与えられた例は、アヒルがを継承する方法FlyBehaviourを実際に示しています。彼らはエクステンドをクラスにすることもできたかもしれませんが、そうすると、貧弱な.QuackBehaviourRubberDuckDuckBirdDuckRubberDuck

于 2014-02-17T09:17:07.893 に答える
19

技術的に言えば、一度に 1 つのクラスしか拡張できず、複数のインターフェイスを実装できますが、ソフトウェア エンジニアリングを実践するときは、一般的に答えられない問題固有のソリューションを提案したいと思います。ちなみに、具体的なクラスを拡張しない/抽象クラスのみを拡張して、不要な継承動作を防ぐことは、オブジェクト指向の良い習慣です。「動物」などは存在せず、動物オブジェクトは使用されず、具体的な動物のみが使用されます。

于 2014-02-17T08:55:32.953 に答える
14

Java 8 以降では、デフォルトのメソッドを使用して、一種の C++ のような多重継承を実現できました。公式ドキュメントよりも簡単に作業を開始できるはずのいくつかの例を示すこのチュートリアルもご覧ください。

于 2014-02-17T10:03:04.240 に答える
12

馬は半ドアを越えることができないため、半ドアのある厩舎で馬を飼うのが安全です。そこで、どんなタイプの馬でも受け入れて半ドアの厩舎に入れる馬舎サービスを立ち上げました。

では、馬でさえ空を飛べる馬のような動物なのだろうか?

以前は多重継承についてよく考えていましたが、プログラミングを 15 年以上続けている今では、多重継承の実装については気にしなくなりました。

多くの場合、多重継承に向けられた設計に対処しようとしたときに、後で問題のドメインを理解していなかったことに気が付きました。

また

それがアヒルのように見え、アヒルのように鳴くのにバッテリーが必要な場合は、おそらく間違った抽象化を持っています

于 2014-02-18T12:33:27.200 に答える
4

ええと、あなたのクラスは他の 1 つのクラスのサブクラスにしかなれませんが、必要に応じていくつでもインターフェイスを実装できます。

ペガサスは実際には馬 (馬の特別なケース) であり、空を飛ぶことができます (これはこの特別な馬の「スキル」です)。一方、ペガサスは鳥であり、歩くことができ、4 本足であると言えます。すべては、コードをどのように簡単に記述できるかにかかっています。

あなたの場合のように、次のように言うことができます:

abstract class Animal {
   private Integer hp = 0; 
   public void eat() { 
      hp++; 
   }
}
interface AirCompatible { 
   public void fly(); 
}
class Bird extends Animal implements AirCompatible { 
   @Override
   public void fly() {  
       //Do something useful
   }
} 
class Horse extends Animal {
   @Override
   public void eat() { 
      hp+=2; 
   }

}
class Pegasus extends Horse implements AirCompatible {
   //now every time when your Pegasus eats, will receive +2 hp  
   @Override
   public void fly() {  
       //Do something useful
   }
}
于 2014-02-17T08:59:43.550 に答える
4

インターフェイス階層を作成して、選択したインターフェイスからクラスを拡張できます。

public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

次に、特定のインターフェイスを拡張して、必要に応じてクラスを定義します。

public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}
于 2014-02-17T09:00:02.747 に答える
3

インターフェイスは多重継承をシミュレートしません。Java の作成者は多重継承は間違っていると考えていたため、Java にはそのようなものはありません。

2 つのクラスの機能を 1 つに結合する場合は、オブジェクト構成を使用します。いえ

public class Main {
    private Component1 component1 = new Component1();    
    private Component2 component2 = new Component2();
}

また、特定のメソッドを公開する場合は、それらを定義して、呼び出しを対応するコントローラーに委譲させます。

ここでインターフェースが便利になるかもしれません - もしComponent1インターフェースInterface1Component2implementsを実装するならInterface2、あなたは定義することができます

class Main implements Interface1, Interface2

コンテキストが許す限り、オブジェクトを交換可能に使用できるようにします。

ですから、私の見解では、ダイヤモンドの問題に取り掛かることはできません。

于 2014-02-17T08:59:07.063 に答える
2

複雑さを軽減して言語を単純化するために、Java では多重継承はサポートされていません。

A、B、および C が 3 つのクラスであるシナリオを考えてみましょう。C クラスは、A クラスと B クラスを継承します。A クラスと B クラスが同じメソッドを持っていて、それを子クラス オブジェクトから呼び出すと、A クラスまたは B クラスのメソッドの呼び出しが曖昧になります。

コンパイル時エラーは実行時エラーよりも優れているため、2 つのクラスを継承すると Java はコンパイル時エラーをレンダリングします。したがって、メソッドが同じであろうと異なっていようと、コンパイル時エラーが発生します。

class A {  
    void msg() {
        System.out.println("From A");
    }  
}

class B {  
    void msg() {
        System.out.println("From B");
    }  
}

class C extends A,B { // suppose if this was possible
    public static void main(String[] args) {  
        C obj = new C();  
        obj.msg(); // which msg() method would be invoked?  
    }
} 
于 2015-02-24T14:33:10.753 に答える
2

Javaの多重継承の問題を解決するには→インターフェースを使う

J2EE (core JAVA) ノート KVR氏 51ページ

日 - 27

  1. インターフェイスは基本的に、ユーザー定義のデータ型を開発するために使用されます。
  2. インターフェイスに関しては、多重継承の概念を実現できます。
  3. インターフェイスを使用すると、ポリモーフィズム、動的バインディングの概念を実現できるため、メモリ空間と実行時間の順番で Java プログラムのパフォーマンスを向上させることができます。

インターフェースは、純粋に未定義のメソッドのコレクションを含む構成体であるか、インターフェースは純粋に抽象メソッドのコレクションです。

[...]

日 - 28:

インターフェイスの機能をクラスに再利用するための構文 1:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
    variable declaration;
    method definition or declaration;
};

上記の構文で、clsname は、「n」個のインターフェイスから機能を継承しているクラスの名前を表します。「Implements」は、インターフェイスの機能を派生クラスに継承するために使用されるキーワードです。

[...]

構文-2 'n' 個のインターフェースを別のインターフェースに継承:

interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{     
    variable declaration cum initialization;
    method declaration;
};

[...]

構文-3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
  variable declaration;
  method definition or declaration;
};
于 2014-02-19T17:49:14.080 に答える