web-dev-qa-db-ja.com

既存のクラスがある場合、インターフェイスは多重継承の必要性をどのように置き換えることができますか

まず第一に...この投稿で申し訳ありません。私は、多重継承について議論しているstackoverflowに関する多くの投稿があることを知っています。しかし、Javaは多重継承をサポートしていないことをすでに知っています。インターフェースを使用することは代替手段であるべきだということを知っています。

Javaで書かれた非常に大きく複雑なツールを変更する必要があります。このツールには、リンクされたメンバー階層を持つさまざまなクラスオブジェクトで構築されたデータ構造があります。とにかく...

  • 複数のメソッドを持ち、オブジェクトのクラスに応じてオブジェクトタグを返すTaggedというクラスが1つあります。メンバーと静的変数が必要です。
  • また、XMLElementという2番目のクラスにより、オブジェクトをリンクし、最終的にXMLファイルを生成できます。ここにはメンバー変数と静的変数も必要です。
  • 最後に、ほとんどすべてがXMLElementといくつかのTaggedを拡張する多くのデータクラスを持っています。

わかりました、1つのクラスだけを拡張することしかできないため、これは機能しません。私は、Javaですべてが大丈夫であり、多重継承を持つ必要がないことを非常に頻繁に読みました。

  1. 実際の実装をすべてのデータクラスに配置することは意味がありません。これは毎回同じですが、これはインターフェイスでは必要だと思います(私は思う)。
  2. 継承クラスの1つをインターフェイスに変更する方法がわかりません。ここに変数があり、それらは正確に存在しなければなりません。

私は本当にそれを理解していないので、誰かがこれを処理する方法を説明してもらえますか?

53
fpdragon

おそらく、継承よりも構成(および委任)を優先する必要があります。

public interface TaggedInterface {
    void foo();
}

public interface XMLElementInterface {
    void bar();
}

public class Tagged implements TaggedInterface {
    // ...
}

public class XMLElement implements XMLElementInterface {
    // ...
}

public class TaggedXmlElement implements TaggedInterface, XMLElementInterface {
    private TaggedInterface tagged;
    private XMLElementInterface xmlElement;

    public TaggedXmlElement(TaggedInterface tagged, XMLElementInterface xmlElement) {
        this.tagged = tagged;
        this.xmlElement = xmlElement;
    }

    public void foo() {
        this.tagged.foo();
    }

    public void bar() {
        this.xmlElement.bar();
    }

    public static void main(String[] args) {
        TaggedXmlElement t = new TaggedXmlElement(new Tagged(), new XMLElement());
        t.foo();
        t.bar();
    }
}
56
JB Nizet

実際、Java SHOULD has Multiple Inheritance以外に良い答えはありません。インターフェースが多重継承の必要性を置き換えることができるという点は、十分な回数繰り返されると真実になるという大きな嘘のようなものです。

引数は、多重継承がこれらすべての問題(la-di-dah)を引き起こすが、C++を使用したことがないJava開発者からこれらの引数を聞き続けるということです。また、C++プログラマーが「うん、C++が大好きなんだけど、もし彼らが多重継承を取り除けば、それは素晴らしい言語になるだろう」と言ったことを覚えていない。人々はそれが実用的であるときにそれを使用し、そうでないときは使用しませんでした。

問題は、多重継承が適切な典型的なケースです。コードをリファクタリングするための提案は、Javaに多重継承がないという問題を回避する方法を実際に示しています。

また、「ああ、代表団のほうがいい、la-di-dah」という議論はすべて、宗教とデザインを混同している。正しい方法はありません。物事はより有用であるか、あまり有用でないかのいずれかであり、それがすべてです。

あなたの場合、多重継承の方が便利で洗練されたソリューションになります。

Multiple Inheritanceを使用したことがなく、「Multiple Inheritance is bad」と信じているすべての宗教的な人々を満足させるために、コードをあまり有用でない形式にリファクタリングする限り、Javaすぐにその方法で「改善」します。宗教的なマントラを愚かさの点まで繰り返している人が多すぎて、それが言語に追加されるのを見ることができません。

実際、あなたに対する私のソリューションは「x extends Tagged、XMLElement」であり、それがすべてです。

...しかし、上記のソリューションからわかるように、ほとんどの人は、そのようなソリューションは複雑すぎて混乱させると思います!

ほとんどのJavaプログラマーの能力を圧倒するような非常に恐ろしい解決策であったとしても、私は自分で "x extends a、b"領域に飛び込むことを好むでしょう。

上記で提案した解決策についてさらに驚くべきことは、多重継承が悪いためにコードを「委任」にリファクタリングすることを提案したすべての人が、同じ問題に直面した場合、単に実行することで問題を解決することです:「xはa、bを拡張し、それで完了します。」「委任vs継承」についての彼らの宗教的議論はすべてなくなります。議論全体は愚かで、本を読み解くことができるか、自分で考えられることがどれほど少ないかを示すだけの無知なプログラマーによってのみ進められています。

Multiple Inheritanceが役立つことは100%正しいです。いいえ、Javaにあるべきだと思うなら、あなたはコードに何か悪いことをしていることになります。

74
SouthRoad

Andreas_Dが推奨 に似ていますが、内部クラスを使用しています。このようにして、各クラスを実際に拡張し、必要に応じて独自のコードでオーバーライドできます。

interface IBird {
    public void layEgg();
}

interface IMammal {
    public void giveMilk();
}

class Bird implements IBird {
    public void layEgg() {
        System.out.println("Laying eggs...");
    }
}

class Mammal implements IMammal {
    public void giveMilk() {
        System.out.println("Giving milk...");
    }
}

class Platypus implements IMammal, IBird {

    private class LayingEggAnimal extends Bird {}
    private class GivingMilkAnimal extends Mammal {}

    private LayingEggAnimal layingEggAnimal = new LayingEggAnimal();

    private GivingMilkAnimal givingMilkAnimal = new GivingMilkAnimal();

    @Override
    public void layEgg() {
        layingEggAnimal.layEgg();
    }

    @Override
    public void giveMilk() {
        givingMilkAnimal.giveMilk();
    }
}
5
SHOJAEI BAGHINI

最初はすべてのデータクラスに実際の実装を配置することは意味がありません。毎回同じですが、これはインターフェイスで必要です(と思います)。

タグの集約を使用してはどうですか?

  1. Taggedクラスの名前をTagsに変更します。

  2. Taggedインターフェイスを作成します。

    インターフェースTagged {Tags getTags(); }

  3. 「タグ付け」が必要な各クラスにTaggedを実装し、tagsから返されるgetTagsフィールドを持たせます。

2番目に、継承クラスの1つをインターフェイスに変更する方法がわかりません。ここに変数があり、そこに正確にある必要があります。

そうです、インターフェイスはインスタンス変数を持つことができません。ただし、タグを格納するデータ構造は、必ずしもIMOがタグ付けされるクラスの一部である必要はありません。別のデータ構造のタグを抽出します。

4
aioobe

TaggedおよびXMLElementクラスのインターフェイスを抽出します(パブリックインターフェイスのすべてのメソッドが必要なわけではありません)。次に、両方のインターフェイスを実装し、実装クラスにTagged(実際の具体的なTaggedクラス)とXMLElement(実際の具体的なXMLElementクラス)を含めます。

 public class MyClass implements Tagged, XMLElement {

    private Tagged tagged;
    private XMLElement xmlElement;

    public MyClass(/*...*/) {
      tagged = new TaggedImpl();
      xmlElement = new XMLElementImpl();
    }

    @Override
    public void someTaggedMethod() {
      tagged.someTaggedMethod();
    }
  }

  public class TaggedImpl implements Tagged {
    @Override
    public void someTaggedMethod() {
      // so what has to be done
    }
  }

  public interface Tagged {
     public void someTaggedMethod();
  }

(およびXMLElementについても同じ)

2
Andreas_D

1つの可能な方法。

1-一般的な機能の基本クラスを作成し、インスタンス化する必要がない場合は抽象クラスにすることができます。

2-インターフェイスを作成し、それらの基本クラスにそれらのインターフェイスを実装します。特定の実装が必要な場合は、メソッドを抽象化します。各具象クラスには独自の実装があります。

3-具象クラスの抽象基本クラスを拡張し、このレベルでも特定のインターフェイスを実装します

1
fmucar

インターフェースと単一の基本クラスを使用すると、単純に次のように述べることができます。

    A)1つのオブジェクトの種類は1つのみです(実際の生活では、ハトは鳥、トヨタは車などです)。ハトも動物ですが、とにかくすべての鳥は動物です。鳥のタイプの上に階層的に-そしてあなたのOOP designで、あなたがそれを表現する必要がある場合に備えて、AnimalクラスはBirdクラスのベースでなければなりません-)
    B)多くの異なることをすることができます(鳥は歌うことができます、飛ぶことができます。車は走ることができます、停止することができます、等)。

オブジェクトが複数のタイプ(水平方向)になりうる世界では、イルカは哺乳類であり、海の動物でもあるとしましょう。この場合、多重継承はより理にかなっています。多重継承を使用して表現する方が簡単です。

1
Jeff Schreib

単純に内部(メンバー)クラス(LRM 5.3.7)を使用できないのではないかと考えていますか?例えば。このような(上記の最初の回答に基づいて):

// original classes:
public class Tagged {
    // ...
}

public class XMLElement {
    // ...
}

public class TaggedXmlElement {
    public/protected/private (static?) class InnerTagged extends Tagged {
      // ...
    }

    public/protected/private (static?) class InnerXmlElement extends XMLElement  {
        // ...
    }

}

この方法では、2つの元のクラスのすべての要素を実際に含むクラスTaggedXmlElementがあり、TaggedXmlElement内では、メンバークラスの非プライベートメンバーにアクセスできます。もちろん、「スーパー」は使用しませんが、メンバークラスメソッドを呼び出します。あるいは、一方のクラスを拡張し、もう一方をメンバークラスにすることもできます。いくつかの制限がありますが、それらはすべて回避できると思います。

1
Einar H.

Androidでも同様の問題が発生します。追加の機能を使用して、ButtonとTextView(両方ともViewから継承)を拡張する必要がありました。スーパークラスにアクセスできないため、別のソリューションを見つける必要がありました。すべての実装をカプセル化する新しいクラスを作成しました。

class YourButton extends Button implements YourFunctionSet {
    private Modifier modifier;

    public YourButton(Context context) {
        super(context);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        modifier = new Modifier(this);
    }

    @Override
    public void generateRandomBackgroundColor() {
        modifier.generateRandomBackgroundColor();
    }
}

class Modifier implements YourFunctionSet {

    private View view;

    public Modifier(View view) {
        this.view = view;
    }

    @Override
    public void generateRandomBackgroundColor() {
        /**
         * Your shared code
         *
         * ......
         *
         * view.setBackgroundColor(randomColor);
         */
    }
}


interface YourFunctionSet {
    void generateRandomBackgroundColor();
}

ここでの問題は、クラスに同じスーパークラスが必要なことです。別のクラスを使用することもできますが、たとえば、どのタイプからのものかを確認してください

public class Modifier{
private View view;
private AnotherClass anotherClass;

public Modifier(Object object) {
    if (object instanceof View) {
        this.view = (View) object;
    } else if (object instanceof AnotherClass) {
        this.anotherClass = (AnotherClass) object;
    }
}

public void generateRandomBackgroundColor(){
    if(view!=null){
        //...do
    }else if(anotherClass!=null){
        //...do
    }
}
}

つまり、基本的には、すべての実装をカプセル化するクラスであるModifierクラスです。

これが誰かを助けることを願っています。

0

別の開発者が提案したように、合成を使用する方法があります。多重継承に対する主な論点は、同じメソッド宣言(同じメソッド名とパラメーター)を持つ2つのクラスから拡張するときに作成されるあいまいさです。しかし、個人的には、それはがらくたの山だと思います。この状況では、コンパイルエラーが簡単にスローされる可能性があります。これは、単一のクラスで同じ名前の複数のメソッドを定義することと大差ありません。次のコードスニペットのようなものは、このジレンマを簡単に解決できます。

public MyExtendedClass extends ClassA, ClassB {
    public duplicateMethodName() {
        return ClassA.duplicateMethodName();
    }
}

多重継承に対するもう1つの引数は、Javaはアマチュア開発者が乱雑で混乱しやすいソフトウェアシステムを作成する可能性のある相互依存クラスのWebを作成しないように、物事をシンプルにしようとしたことです。さらに、この引数はコーディングの他の100の項目に使用される可能性があるため、開発チームはコードレビュー、スタイルチェックソフトウェア、ナイトリービルドを使用できます。

ただし、特定の状況では、作曲で解決する必要があります(Shojaei Baghiniの回答を参照)。ボイラープレートコードを少し追加しますが、多重継承と同じ動作をエミュレートします。

0
Greg H