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

Posted

技术标签:

【中文标题】当有现有类时,接口如何取代对多重继承的需求【英文标题】:How can interfaces replace the need for multiple inheritance when have existing classes 【发布时间】:2011-06-27 13:26:01 【问题描述】:

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

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

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

好的,这行不通,因为只能扩展一个类。我经常读到,Java 的一切都很好,不需要多重继承。我相信,但我不明白接口应该如何取代继承。

    将真正的实现放在所有数据类中是没有意义的,因为它每次都是一样的,但对于接口来说这是必要的(我认为)。 我不知道如何将我的继承类之一更改为接口。我在这里有变量,它们必须准确无误。

我真的不明白,所以请有人解释一下如何处理这个问题?

【问题讨论】:

看看这个帖子***.com/questions/3556652/… 您说“我有这么多数据类,几乎所有这些数据类都应该扩展 XMLElement,其中一些是 Tagged”和“将真正的实现放在所有数据类中是没有意义的,因为它是相同的每次,但这对于接口来说是必要的(我认为)。 ---那么你的问题首先是不正确的:类是一种类型;如果需要许多数据类,那么每个数据类都应该有自己的特定行为。换句话说,每个数据类都应该覆盖基类中的成员函数。对立也成立。 【参考方案1】:

使用接口和单个基类,您只是在说明:

A) 一个对象只能是一种类型(如果您认为,这在现实生活中是正确的, 鸽子是鸟,丰田是汽车,等等。鸽子也是动物,但无论如何每只鸟都是动物,所以它在层次上高于鸟类型 - 在你的 OOP 设计中,动物类应该是鸟类的基础如果你需要代表它-) 和

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

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

【讨论】:

【参考方案2】:

类似于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】:

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

希望这对某人有所帮助。

【讨论】:

【参考方案4】:

正如另一位开发人员所建议的那样,使用组合将是一种可行的方法。反对多重继承的主要论据是当您从具有相同方法声明(相同方法名称和参数)的两个类扩展时产生的歧义。然而,就个人而言,我认为这是一堆废话。在这种情况下很容易引发编译错误,这与在单个类中定义多个同名方法没有太大区别。类似下面的代码 sn-p 可以轻松解决这个难题:

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

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

但是,在您的特定情况下,您必须解决构图问题(请参阅 Shojaei Baghini 的回答)。它添加了一些样板代码,但它模拟了与多重继承相同的行为。

【讨论】:

【参考方案5】:

只是想知道是否不能简单地使用内部(成员)类(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 中,你可以访问成员类的非私有成员。当然不会使用“super”,而是调用成员类方法。 或者,可以扩展其中一个类并使另一个成为成员类。 有一些限制,但我认为它们都可以解决。

【讨论】:

【参考方案6】:

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

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

【讨论】:

我能问一个愚蠢的问题吗?上述TaggedXmlElement类的构造函数在哪里? 现在它就在那里。感谢您的关注。【参考方案7】:

实际上,除了 Java 应该有多重继承之外,我没有什么好的答案。接口应该能够取代对多重继承的需求这一点就像一个大谎言,当重复足够多的时间就会成为真的。

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

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

还有所有关于“哦,授权更好,la-di-dah”的讨论都将宗教与设计混为一谈。没有正确的方法。事物要么更有用,要么更没用,仅此而已。

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

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

实际上,我的解决方案是“x extends Tagged, XMLElement”,仅此而已。

...但是从上面提供的解决方案中可以看出,大多数人认为这样的解决方案太复杂和令人困惑!

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

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

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

【讨论】:

这应该是最佳答案:) 阿门!对于血腥的 c# 和 .net 人来说,这是真的。 Bertrand Meyer 曾经说过,.net 和 java 没有多重继承的唯一原因是难以编写支持多个基类的类加载器。 @KemalErdogan:在 C++ 中,您可以将任何指针直接转换为***对象类型,然后直接向下转换为其实际类型或其任何超类型吗?这种能力,以及向上转换和向下转换是保持身份的事实,是 Java 设计的基础。 这个答案有偏见,在这种特殊情况下很可能是错误的。 “您的问题是适合多重继承的经典案例。” --- 首先,您不确定这几行描述有什么问题。其次,这个问题听起来根本不像一个经典案例。规则 2(parashift.com/c++-faq/mi-disciplines.html) 有充分的理由。 Tagged 和 XMLElement 听起来根本不像 ABC。实际上,适当的 C++ 多重继承范式首先具有实现多个接口的想法。 为了支持这个答案,专有代码可能希望您的对象是特定类的实例。如果两段专有代码期望同一个对象属于两个类,而不是另一个类的实例,那么唯一的选择可能是使用一种极其丑陋的技巧,即用接口替换类,同时希望所有方法都兼容。【参考方案8】:

一种可能的方式;

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

2- 创建接口并在这些基类中实现这些接口。如果需要具体实现,则将方法抽象化。每个具体类都可以有自己的实现。

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

【讨论】:

【参考方案9】:

我会这样解决:为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 也是如此)

【讨论】:

【参考方案10】:

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

对标签使用聚合怎么样?

    Tagged 类重命名为Tags

    创建Tagged接口:

    接口标记 标签 getTags();

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

其次,我不明白如何将我的继承类之一更改为接口。我这里有变量,它们必须在那里。

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

【讨论】:

以上是关于当有现有类时,接口如何取代对多重继承的需求的主要内容,如果未能解决你的问题,请参考以下文章

delete如何找到多重继承对象的内存块起始地址

“独占”继承:对某些形式的多重继承的要求

多继承 与 多重继承

为啥要使用接口,多重继承与接口,接口的好处?

使用与多重继承相关的接口的任何真实示例

C ++如何从具有不同返回类型的接口多重继承?