티스토리 뷰

반응형

다이아몬드 문제

다중 상속을 지원하게 되면 하나의 클래스가 여러 상위 클래스를 상속 받을 수 있습니다. 이런 특징 때문에 발생하게 되는 문제가 있는데, 바로 '다이아몬드 문제' 입니다.



위의 클래스 다이어그램과 같은 상속 구조에서 발생되는 문제가 다이아몬드 문제입니다. 마치 모양이 다이아몬드 모양이라서 붙여진 이름 같습니다. 예를들어 GrandFather이라는 클래스가 myMethod() 라는 이름의 메소드를 가지고 있다고 가정해봅시다. 그리고 FatherA와 FatherB가 각각 오버라이딩하여 구현하였다면, FatherA와 FatherB를 모두 상속받은 Son 클래스 입장에서는 어떤 부모의 myMethod()를 사용해야 할까요? 이로 인하여 충돌이 생기게 됩니다.


코드로 나타내면 아래와 같은 모습입니다. 

class GrandFather {
    void myMethod(){
        System.out.println("GrandFather");
    }
}

class FatherA extends GrandFather {
    @Override
    void myMethod(){
        System.out.println("FatherA");
    }
}

class FatherB extends GrandFather {
    @Override
    void myMethod(){
        System.out.println("FatherB");
    }
}

class Son extends FatherA, FatherB{
    @Override
    void myMethod() {
        super.myMethod(); //FatherA를 출력해야 할까? FatherB를 출력해야 할까?
    }
}

Son클래스 입장에서는 같은 이름의 myMethod가 두개의 상위 클래스에 모두 정의되어 있기 때문에, 어떤 메소드를 실행해야 될지 알 수가 없습니다. (그리고 위의 코드는 당연히 컴파일 되지 않습니다.)

같은 객체지향 언어인 C++에서는 이런 문제가 있음에도 불구하고 개발자에게 일임하고, 자바는 내부적으로 구현이 불가하도록 막아두었습니다. (C나 C++은 좀 더 개발자들을 믿고 자유를 주는 반면에 JAVA는 개발 편의성을 생각하는 언어라는 사실을 이 점에서도 알 수 있습니다.)

자바도 인터페이스는 다중상속 되던데?

한가지 의아한 점이 생길 수 있습니다. 저런 문제가 있음에도 불구하고 자바에서 인터페이스는 다중상속이 가능하기 때문입니다. 하지만 이는 실제로 구현해보면 전혀 문제를 발생시키지 않는다는 것을 알 수 있습니다.

interface GrandFather {
    void myMethod();
}

interface FatherA extends GrandFather {
    @Override
    void myMethod();
}

interface FatherB extends GrandFather {
    @Override
    void myMethod();
}

interface Son extends FatherA, FatherB{
    @Override
    void myMethod();//상위 인터페이스에서 구현된 것이 없기 때문에 충돌이 발생하지 않는다.
}

인터페이스는 기능에 대한 선언만 해두면 되기 때문에, 다이아몬드 상속이 되더라도 충돌할 여지가 전혀 없습니다. 그러므로 인터페이스에 경우는 다중상속을 통한 문제가 발생하지 않습니다.

자바8의 default method는?

자바8에서는 default method가 추가되었기 때문에, 내부적으로 코드 구현이 가능합니다. 이런 경우는 마치 class 처럼 다중상속을 받을 수 없게 됩니다.

interface GrandFather {
    default void myMethod(){
        System.out.println("GrandFather");
    };
}

interface FatherA extends GrandFather {
    @Override
    default void myMethod(){
        System.out.println("FatherA");
    }
}

interface FatherB extends GrandFather {
    @Override
    default void myMethod(){
        System.out.println("FatherB");
    }
}

interface Son extends FatherA, FatherB{
     //컴파일 에러 발생 
}

Class의 다중 상속처럼 컴파일이 진행되지 않습니다. 다만 myMethod()를 새롭게 오버라이딩 하면 충돌을 해결 할 수 있습니다.

반응형
댓글
  • 프로필사진 234 잘보고갑니당. 2020.03.03 09:10
  • 프로필사진 사용자 siyoon210 댓글 감사합니다. :) 2020.03.03 09:53 신고
  • 프로필사진 kikiruk JAVA 의 이점은 참 마음에 듭니다 하지만 C++ 을 쓰는 사람도 다중상속 자체를 잘 안쓰기때문에 크게 상관은없고, 큰 프로젝트를 하는 사람들끼리는
    '순수 가상 클래스' 가 아니면 다중상속을 하지말자는 코딩 규칙을 기반으로 할겁니다. 그걸 모르는 사람은 없기때문에 크게 상관없다고 볼수 있겠지요
    참고로C++ 의 '순수 가상 클래스' 에대해서 말씀드리자면 클래스의 멤버 함수가 '순수 가상 함수' 를 하나라도 포함한다면 그것을 두고 '가상 클래스' 라고
    하며, 모든 멤버함수가 하나도빠지지않고 '순수 가상함수' 일시에는 그 클래스를 두고 '순수 가상 클래스' 라고 합니다. 쉽게말하면 이게 인터페이스이죠.
    참고로 '순수 가상 함수' 의 구현 방법은 함수의 선언부만 있고 구현부는 없으며 선언부에 void function() = 0; 과같이 = 0 을 붙여두면 됩니다.
    좋은글 잘보고갑니다.
    2020.04.15 21:57
  • 프로필사진 사용자 siyoon210 오 저는 c++에 대한 경험이 적어서 잘 몰랐는데, c++ 실무에서도 다중상속을 컨벤션으로 막아두는 군요. :) 댓글 감사합니다. 2020.04.16 09:48 신고
댓글쓰기 폼