Java中的多重继承与组合vs继承

本文探讨了Java中不支持类的多重继承的原因,如钻石问题,并展示了接口如何支持多重继承。强调了组合优于继承的几个方面,包括避免编译歧义、提高代码灵活性和单元测试的便利性。文章建议在"is-a"关系成立时使用继承,否则应优先考虑组合。
摘要由CSDN通过智能技术生成

有时我写了几篇有关Java 继承接口组成的文章。 在这篇文章中,我们将研究多重继承,然后学习组成优于继承的好处。

Java中的多重继承

多重继承是创建具有多个超类的单个类的能力。 与其他一些流行的面向对象的编程语言(例如C ++)不同, java不提供对类中多重继承的支持 。 Java不支持类中的多重继承,因为它可能导致菱形问题 ,而不是提供解决复杂问题的方法,还有更好的方法来实现与多重继承相同的结果。

钻石问题

为了轻松理解钻石问题,我们假设Java支持多重继承。 在这种情况下,我们可以像下面的图像那样有一个类层次结构。

钻石问题多重继承

假设SuperClass是一个抽象类,声明了一些方法,而ClassA,ClassB是具体类。

超类.java
package com.journaldev.inheritance;

public abstract class SuperClass {

	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;

public class ClassA extends SuperClass{

	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of A");
	}

	//ClassA own method
	public void methodA(){

	}
}
ClassB.java
package com.journaldev.inheritance;

public class ClassB extends SuperClass{

	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of B");
	}

	//ClassB specific method
	public void methodB(){

	}
}

现在,让我们说ClassC的实现如下所示,它扩展了ClassA和ClassB。

ClassC.java
package com.journaldev.inheritance;

public class ClassC extends ClassA, ClassB{

	public void test(){
		//calling super class method
		doSomething();
	}

}

注意, test()方法正在调用超类doSomething()方法,这导致歧义,因为编译器不知道要执行哪个超类方法,并且由于菱形类图,它被称为Diamond Problem,这是Java不支持类中的多重继承的主要原因。

请注意,上述具有多类继承的问题也可能只出现在三个类中,它们全部具有至少一个通用方法。

接口中的多重继承

您可能已经注意到,我一直在说类不支持多重继承,但接口支持多重继承,并且单个接口可以扩展多个接口,下面是一个简单的示例。

接口A.java
package com.journaldev.inheritance;

public interface InterfaceA {

	public void doSomething();
}
接口B.java
package com.journaldev.inheritance;

public interface InterfaceB {

	public void doSomething();
}

注意,两个接口都声明了相同的方法,现在我们可以有一个扩展这两个接口的接口,如下所示。

接口C.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();

}

这样做非常好,因为接口仅声明方法,并且实际实现将由实现接口的具体类来完成,因此在接口的多重继承中不存在任何歧义的可能性。

这就是为什么Java类可以实现多重继承的原因,例如下面的示例。

接口Impl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

	@Override
	public void doSomething() {
		System.out.println("doSomething implementation of concrete class");
	}

	public static void main(String[] args) {
		InterfaceA objA = new InterfacesImpl();
		InterfaceB objB = new InterfacesImpl();
		InterfaceC objC = new InterfacesImpl();

		//all the method calls below are going to same concrete implementation
		objA.doSomething();
		objB.doSomething();
		objC.doSomething();
	}

}

您是否注意到,每当我覆盖任何超类方法或实现任何接口方法时,我都使用@Override注释,它是三个内置的Java注释之一,并且在覆盖任何方法时都应始终使用覆盖注释

救援人员组成

因此,如果我们想在ClassC利用ClassA函数methodA()ClassB函数methodB() ,该解决方案在于使用composition ,这是ClassC的重构版本,该版本使用了composition来同时利用类方法和doSomething ()方法来自其中一个对象。

ClassC.java
package com.journaldev.inheritance;

public class ClassC{

	ClassA objA = new ClassA();
	ClassB objB = new ClassB();

	public void test(){
		objA.doSomething();
	}

	public void methodA(){
		objA.methodA();
	}

	public void methodB(){
		objB.methodB();
	}
}

组合与继承

Java编程的最佳实践之一是“通过接口支持组合”,我们将研究一些偏爱这种方法的方面。

  1. 假设我们有一个超类和子类,如下所示:
    ClassC.java
    package com.journaldev.inheritance;
    
    public class ClassC{
    
    	public void methodC(){
    	}
    }
    ClassD.java
    package com.journaldev.inheritance;
    
    public class ClassD extends ClassC{
    
    	public int test(){
    		return 0;
    	}
    }

    上面的代码可以编译并正常工作,但是如果ClassC实现更改如下,该怎么办:

    ClassC.java
    package com.journaldev.inheritance;
    
    public class ClassC{
    
    	public void methodC(){
    	}
    
    	public void test(){
    	}
    }

    请注意,子类中已经存在test()方法,但是返回类型有所不同,现在ClassD将无法编译,并且如果您使用的是任何IDE,它将建议您更改超类或子类中的返回类型。

    现在想象一下这样的情况:我们具有多个级别的类继承,并且超类不受我们控制,我们别无选择,只能更改子类方法签名或名称以消除编译错误,我们还必须在所有方面进行更改子类方法被调用的地方,因此继承使我们的代码易碎。

    组合永远不会发生上述问题,这使其比继承更有利。

  2. 继承的另一个问题是,我们将所有超类方法公开给客户端,并且如果我们的超类设计不当且存在安全漏洞,那么即使我们在实现类时全力以赴,我们也会受到不佳实现的影响。超类。
    组合可以帮助我们提供对超类方法的受控访问,而继承不提供对超类方法的任何控制,这也是组合优于继承的主要优势之一。
  3. 组合的另一个好处是它提供了方法调用的灵活性。 我们上ClassC实现不是最佳的,它提供了与将要调用的方法的编译时绑定,只需进行很小的更改,我们就可以灵活地使方法调用并使之动态。
    ClassC.java
    package com.journaldev.inheritance;
    
    public class ClassC{
    
    	SuperClass obj = null;
    
    	public ClassC(SuperClass o){
    		this.obj = o;
    	}
    	public void test(){
    		obj.doSomething();
    	}
    
    	public static void main(String args[]){
    		ClassC obj1 = new ClassC(new ClassA());
    		ClassC obj2 = new ClassC(new ClassB());
    
    		obj1.test();
    		obj2.test();
    	}
    }

    上面程序的输出是:

    doSomething implementation of A
    doSomething implementation of B

    这种方法调用的灵活性在继承中不可用,从而提倡了最佳做法,即在继承方面偏向于组合。

  4. 单元测试很容易组合,因为我们知道超类中正在使用的所有方法,并且可以对其进行模拟,而在继承中,我们很大程度上依赖于超类,并且不知道将使用超类的所有方法,因此我们需要要测试超类的所有方法,这是一项额外的工作,由于继承,我们不需要这样做。

理想情况下,只有在所有情况下父类和子类的“ is-a ”关系均成立时才应使用继承,否则我们应该继续进行组合。


翻译自: https://www.javacodegeeks.com/2013/08/multiple-inheritance-in-java-and-composition-vs-inheritance.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值