当有现有类时,接口如何取代多重继承的需要

时间:2011-02-15 11:51:56

标签: java inheritance interface multiple-inheritance

首先......对不起这篇文章。我知道stackoverflow上有很多帖子正在讨论多重继承。但我已经知道Java不支持多重继承,我知道使用接口应该是另一种选择。但我不明白并且看到了我的困境:

我必须对用Java编写的非常大且复杂的工具进行更改。在此工具中,有一个数据结构,它由许多不同的类对象构建,并具有链接的成员层次结构。总之...

  • 我有一个类Tagged,它有多个方法,并根据对象的类返回一个对象标记。它需要成员和静态变量。
  • 第二个名为XMLElement的类允许链接对象,最后生成一个XML文件。我还需要成员和静态变量。
  • 最后,我有这么多的数据类几乎都应该扩展XMLElement,其中一些Tagged

好的,这不会起作用,因为它只能扩展一个类。我经常读到Java的所有内容都没问题,也没有必要进行多重继承。我相信,但我不知道接口应如何取代继承。

  1. 将实际实现放在所有数据类中是没有意义的,因为它每次都是相同的,但这对于接口来说是必要的(我认为)。
  2. 我看不出如何将一个继承类更改为接口。我在这里有变量,他们必须完全在那里。
  3. 我真的不明白所以请有人解释我如何处理这个问题?

10 个答案:

答案 0 :(得分:74)

实际上,除了Java应该有多重继承之外,我没有其他好的答案。接口应该能够取代多重继承的要点就像是重复足够多次成为现实的大谎言。

参数是多重继承导致所有这些问题(la-di-dah),但我一直听到那些从未使用过C ++的Java开发人员的论点。我也不记得C ++程序员说“哎呀,我喜欢C ++,但如果他们只能摆脱多重继承,它就会变成一种很棒的语言”。人们在实际时使用它而不是在实际时使用它。

您的问题是多重继承适用的经典案例。任何重构代码的建议都告诉你如何解决Java没有多重继承的问题。

所有关于“哦,代表团更好,la-di-dah”的讨论都让宗教与设计混淆。没有正确的方法。事情要么更有用,要么不那么有用。

在您的情况下,多重继承会更有用,也是更优雅的解决方案。

至于将代码重构为一种不太有用的形式,以满足所有从未使用过多重继承的宗教人士,并相信“多重继承是坏事”,我想你必须降级你的代码因为我看不到Java很快就会以这种方式“改进”。有太多人重复宗教咒语到愚蠢的程度,我无法看到它被添加到语言中。

实际上,我的解决方案是“x extends Tagged,XMLElement”,这就是全部。

...但正如您从上面提供的解决方案中可以看到的那样,大多数人认为这样的解决方案将变得过于复杂和混乱!

我宁愿自己冒险进入“x extends a,b”领域,即使这是一个非常可怕的解决方案,可能会压倒大多数Java程序员的能力。

上面建议的解决方案更令人惊讶的是,这里的每个人都建议您将代码重构为“委托”,因为多重继承很糟糕,如果他们遇到同样的问题,会解决问题通过简单地做:“x扩展a,b”并完成它,所有关于“委托与继承”的宗教争论都将消失。整个辩论都是愚蠢的,只有无知的程序员才会提出,他们只能证明他们能够从书中背诵出来的程度,以及他们能为自己多少思考。

多重继承可以帮助您100%正确,不,如果您认为Java应该拥有它,那么您在代码中做错了什么。

答案 1 :(得分:56)

你可能更喜欢组合(和委托)而不是继承:

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

答案 2 :(得分:5)

Andreas_D suggested类似,但使用内部类。这样,您确实可以扩展每个类,并且可以根据需要在您自己的代码中覆盖它。

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

答案 3 :(得分:4)

  

首先将真正的实现放在所有数据类中是没有意义的,因为它每次都是相同的,但这对于接口来说是必要的(我认为)。

如何为标签使用聚合?

  1. 将您的Tagged课程重命名为Tags

  2. 创建Tagged界面:

    interface Tagged {     标签getTags(); }

  3. 让每个需要“标记”的类,实现Tagged并让它有一个tags字段,该字段从getTags返回。

    < / LI>
      

    其次我不知道如何将我的继承类之一更改为接口。我在这里有变量,他们必须完全在那里。

    没错,接口不能有实例变量。但是,存储标记的数据结构不一定是IMO标记的类的一部分。将标签分解为单独的数据结构。

答案 4 :(得分:2)

我会这样解决:提取TaggedXMLElement类的接口(可能你不需要公共接口中的所有方法)。然后,实现两个接口,实现类具有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相同)

答案 5 :(得分:1)

一种可能的方式;

1-您可以为常用功能创建基类,如果您不需要实例化它,请将其抽象化。

2-创建接口并在这些基类中实现这些接口。如果需要特定实现,请将该方法设为抽象。每个具体类都有自己的impl。

3-为具体类扩展抽象基类,并在此级别实现特定接口

答案 6 :(得分:1)

只是想知道是否不能简单地使用内部(成员)类(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  {
        // ...
    }

}

这样你就有了一个TaggedXmlElement类,它实际上包含两个原始类中的所有元素,而在TaggedXmlElement中你可以访问成员类的非私有成员。当然,不会使用“超级”,而是调用成员类方法。 或者,可以扩展其中一个类,并使另一个成为类成员。 有一些限制,但我认为它们都可以解决。

答案 7 :(得分:1)

使用接口和单个基类,您只需说明一下:

    A)一个物体只能是一种类型(在现实生活中,如果您认为, 鸽子是鸟,丰田是汽车,等等。鸽子也是动物,但无论如何,每只鸟都是动物,因此其层次结构要高于鸟类类型-并且在您的OOP设计中,动物类应该是鸟类类的基础如果您需要代表它-) 和

    B)可以做很多不同的事情(鸟可以唱歌,可以飞行,汽车可以跑步,可以停止等等)。
也适合现实生活中的物体。

在一个对象可以是多种类型(水平)的世界中 假设海豚既是哺乳动物又是海洋动物,在这种情况下,多重继承会更有意义。使用多重继承来表示它会更容易。

答案 8 :(得分:0)

使用合成将是另一个开发人员建议的方式。反对多重继承的主要论据是当您使用相同的方法声明(相同的方法名称和参数)从两个类扩展时产生的歧义。然而,就个人而言,我认为这是一堆垃圾。在这种情况下很容易抛出编译错误,这与在单个类中定义同名的多个方法没有太大区别。类似下面的代码片段可以很容易地解决这个困境:

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

反对多重继承的另一个论点是Java试图保持简单,以便业余开发人员不会创建一个相互依赖的类网络,这可能会造成混乱,混乱的软件系统。但正如您在案例中所看到的那样,当事情不可用时,它也会使事情变得复杂和混淆。此外,该参数可用于编码中的其他100个内容,这就是开发团队拥有代码审查,样式检查软件和夜间构建的原因。

在你的特殊情况下,你将不得不接受作曲(见Shojaei Baghini的回答)。它增加了一些样板代码,但它模仿了与多重继承相同的行为。

答案 9 :(得分:0)

我在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类,它封装了所有实现。

希望这有助于某人。