JavaRush /Java Blog /Random-KO /Java의 다중 상속. 구성과 상속의 비교
HonyaSaar
레벨 26
Москва

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

Random-KO 그룹에 게시되었습니다
얼마 전에 나는 Java의 상속, 인터페이스 및 구성에 관한 여러 게시물을 썼습니다. 이 기사에서는 다중 상속에 대해 살펴본 다음 상속에 비해 구성의 이점에 대해 알아봅니다.
Java의 다중 상속.  구성과 상속의 비교 - 1

자바의 다중 상속

다중 상속은 여러 부모 클래스를 사용하여 클래스를 만드는 기능입니다. C++ 등 널리 사용되는 다른 객체지향 언어와 달리 Java는 다중 클래스 상속을 지원하지 않습니다. 그는 "다이아몬드 문제"가 발생할 가능성 때문에 이를 지원하지 않고 대신 유사한 상속 결과를 얻을 수 있는 최선의 옵션을 사용하여 문제를 해결하기 위한 일종의 포괄적인 접근 방식을 제공하는 것을 선호합니다.

"다이아몬드 문제"

다이아몬드 문제를 더 간단하게 이해하기 위해 Java에서 다중 상속이 지원된다고 가정해 보겠습니다. 이 경우 아래 그림과 같은 계층 구조를 가진 클래스를 얻을 수 있습니다. 가 특정 메서드를 설명하는 추상 클래스이고, 클래스 와 가 실제 클래스라고 다이아몬드 등급 계층가정해 보겠습니다 . SuperClass ClassA ClassB SuperClass.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("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
이제 클래스가 동시에 상속받고 동시에 다음 구현이 있다고 가정 해 ClassC보겠습니다 . ClassA ClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
메서드는 부모 클래스의 test()메서드를 호출하는데 doSomething(), 이는 컴파일러가 어떤 슈퍼클래스 메서드를 호출해야 할지 모르기 때문에 모호하게 됩니다. 이 상황에서 클래스 상속 다이어그램의 모양이 면 처리된 다이아몬드의 윤곽과 유사하기 때문에 이 문제를 "다이아몬드 문제"라고 합니다. 이것이 Java가 다중 클래스 상속을 지원하지 않는 주된 이유입니다. 다중 클래스 상속과 관련된 이 문제는 하나 이상의 공통 메서드가 있는 세 클래스에서도 발생할 수 있습니다.

다중 상속 및 인터페이스

제가 항상 "클래스 간 다중 상속은 지원되지 않습니다"라고 말하지만 인터페이스 간에서는 지원된다는 점을 눈치채셨을 것입니다. 간단한 예가 아래에 나와 있습니다. InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

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

public interface InterfaceB {

    public void doSomething();
}
두 인터페이스 모두 동일한 이름의 메서드를 가지고 있습니다. 이제 두 인터페이스 모두에서 상속되는 인터페이스가 있다고 가정해 보겠습니다. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
여기서 인터페이스는 단지 메소드의 예약/설명일 뿐이고 메소드 자체의 구현은 이러한 인터페이스를 구현하는 구체적인 클래스에 있으므로 인터페이스의 다중 상속으로 인해 모호함이 발생할 가능성이 없으므로 모든 것이 이상적입니다. 이것이 Java의 클래스가 여러 인터페이스에서 상속될 수 있는 이유입니다. 아래 예를 통해 보여드리겠습니다. InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

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

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
내가 슈퍼클래스나 인터페이스에 설명된 메서드를 재정의할 때마다 @Override 주석을 사용한다는 사실을 눈치챘을 것입니다. 이는 내장된 세 가지 Java 주석 중 하나이며 메서드를 재정의할 때 항상 사용해야 합니다.

구원으로서의 구성

그렇다면 에서 methodA()클래스 ClassAmethodB()클래스 함수를 사용하려면 어떻게 해야 할까요 ? 이에 대한 해결책은 두 클래스 메소드를 모두 구현 하고 객체 중 하나에 대한 구현도 포함하는 다시 작성된 버전인 구성일 수 있습니다 . ClassB ClassС ClassC ClassA ClassB 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다르게 구현되면 어떻게 될까요?

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

    해당 메서드는 test()이미 하위 클래스에 존재하지만 다른 유형의 결과를 반환합니다. 이제 ClassDIDE를 사용하는 경우 컴파일되지 않습니다. 하위 클래스 또는 상위 클래스에서 반환 유형을 변경하는 것이 좋습니다.

    이제 클래스의 다중 수준 상속이 있고 변경 사항에 슈퍼클래스를 사용할 수 없는 상황을 상상해 보겠습니다. 이제 컴파일 오류를 제거하려면 하위 클래스 메서드의 서명이나 이름을 변경하는 것 외에는 다른 옵션이 없습니다. 또한 이 메서드가 호출된 모든 위치를 변경해야 합니다. 따라서 상속은 코드를 취약하게 만듭니다.

    위에서 설명한 문제는 합성의 경우에는 결코 발생하지 않으므로 후자가 상속보다 바람직합니다.

  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 " 관계가 참인 경우에만 사용해야 하며 그렇지 않은 경우 구성을 선호해야 합니다.

원본 기사
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION