JavaRush /Java Blog /Random-KO /Java의 다중 상속. 구성과 상속
DSergey_Kh
레벨 12

Java의 다중 상속. 구성과 상속

Random-KO 그룹에 게시되었습니다

자바의 다중 상속

다중 상속을 사용하면 여러 슈퍼클래스에서 상속되는 클래스를 만들 수 있습니다. C++와 같은 다른 인기 있는 객체 지향 프로그래밍 언어와 달리 Java는 클래스로부터의 다중 상속을 허용하지 않습니다. Java는 다중 클래스 상속을 지원하지 않습니다. 다이아몬드 문제가 발생할 수 있기 때문입니다. 그리고 이 문제를 해결하는 방법을 찾는 대신 다중 상속과 같은 동일한 결과를 얻을 수 있는 더 나은 옵션이 있습니다.

다이아몬드 문제

다이아몬드 문제를 더 쉽게 이해하기 위해 Java에서 다중 상속이 지원된다고 가정해 보겠습니다. 이 경우 아래 이미지와 같은 클래스 계층 구조를 가질 수 있습니다. Java의 다중 상속.  구성과 상속 - 1클래스 SuperClass가 추상 클래스이고 그 안에 일부 메서드가 선언되어 있다고 가정해 보겠습니다. 구체적인 클래스 ClassAClassB.
package com.journaldev.inheritance;
public abstract class SuperClass {
	public abstract void doSomething();
}
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(){
	}
}
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에서 이를 구현하고 상속한다고 가정합니다 . ClassAClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
	public void test(){
		//calling super class method
		doSomething();
	}
}
메소드는 test()슈퍼클래스 메소드를 호출합니다 doSomething(). 이는 컴파일러가 어떤 슈퍼클래스 메서드를 실행할지 모르기 때문에 모호해집니다. 이것은 다이아몬드 문제라고 불리는 다이아몬드 모양의 클래스 다이어그램입니다. 이것이 Java가 다중 상속을 지원하지 않는 주된 이유입니다. 다중 클래스 상속과 관련된 위의 문제는 적어도 하나의 공통 메서드가 있는 세 개의 클래스에서만 발생할 수 있습니다.

다중 인터페이스 상속

Java에서는 다중 상속이 클래스에서 지원되지 않지만 인터페이스에서는 지원됩니다. 그리고 하나의 인터페이스는 다른 여러 인터페이스를 확장할 수 있습니다. 아래는 간단한 예입니다.
package com.journaldev.inheritance;
public interface InterfaceA {
	public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
	public void doSomething();
}
두 인터페이스 모두 동일한 메서드를 선언합니다. 이제 아래 예제와 같이 두 인터페이스를 모두 확장하는 인터페이스를 만들 수 있습니다.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();
}
인터페이스는 메서드만 선언하고 인터페이스를 상속하는 클래스에서 구현이 수행되기 때문에 이는 훌륭하게 작동합니다. 따라서 다중 인터페이스 상속에서 모호성을 얻을 방법이 없습니다.
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. methodA()클래스의 함수와 클래스의 ClassA함수를 클래스에서 사용하고 싶다면 어떻게 해야 할까요 ? 해결책은 구성을 사용하는 데 있습니다. 다음은 구성을 사용하여 클래스 메서드와 개체 중 하나의 메서드를 모두 정의하는 클래스 버전입니다 .methodB()ClassBClassCClassCdoSomething()
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. 슈퍼클래스와 이를 확장하는 클래스가 있다고 가정해 보겠습니다.

    package com.journaldev.inheritance;
    public class ClassC{
    	public void methodC(){
    	}
    }
    package com.journaldev.inheritance;
    public class ClassD extends ClassC{
    	public int test(){
    		return 0;
    	}
    }

    위의 코드는 컴파일되고 잘 작동합니다. ClassC그러나 아래와 같이 클래스 구현을 변경하면 어떻게 될까요 ?

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

    해당 메소드는 test()이미 서브클래스에 존재하지만 반환 유형은 다릅니다. 이제 클래스는 ClassD컴파일되지 않으며 IDE를 사용하는 경우 슈퍼클래스 또는 서브클래스에서 반환 유형을 변경하라는 메시지가 표시됩니다.

    이제 다중 레벨 클래스 상속 계층 구조가 있고 슈퍼클래스에 액세스할 수 없는 상황을 상상해 보세요. 컴파일 오류를 제거하려면 하위 클래스 메서드 서명이나 이름을 변경하는 것 외에는 선택의 여지가 없습니다. 또한 호출되는 모든 위치에서 하위 클래스 메서드를 변경해야 합니다. 따라서 상속은 코드를 취약하게 만듭니다.

    위의 문제는 구성에서는 결코 발생하지 않으며 이는 상속에 더 매력적입니다.

  2. 상속의 또 다른 문제는 슈퍼클래스의 모든 메소드를 클라이언트에 노출한다는 것입니다. 슈퍼클래스가 적절하게 설계되지 않았고 보안 허점이 있는 경우 클래스의 최상의 구현을 구현하더라도 잘못된 구현으로 인해 영향을 받습니다. 슈퍼클래스의 . 컴포지션은 슈퍼클래스 메서드에 대한 제어된 액세스를 제공하는 데 도움이 되지만 상속은 슈퍼클래스 메서드에 대한 제어를 제공하지 않습니다. 이는 상속을 통한 구성의 주요 이점 중 하나이기도 합니다.

  3. 구성의 또 다른 장점은 메소드 호출에 유연성이 있다는 것입니다. 위에 제시된 클래스의 구현은 ClassC최적이 아니며 컴파일 시간이 호출될 메서드에 연결되어 있음을 보장합니다. 최소한의 변경만으로 메소드 호출을 유연하고 동적으로 만들 수 있습니다.

    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"로 정의된 경우에만 상속을 사용해야 합니다. 다른 모든 경우에는 구성을 사용하는 것이 좋습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION