프로그래밍 언어에 상속의 기능을 사용할 때 주의해야 할 점이 있는데 다중 상속의 문제점을 피해야 하는 것이다.
exdends 키워드를 이용한 상속이라면 자녀 클래스가 하나의 부모 클래스한테 기능을 상속받아 부모 클래스의 기능을 사용하게 해서 불필요한 코드를 줄이는 중요한 역할도 하며, Abstraction이 가능해져서 더 체계적인 객체지향 코드를 작성할 수 있다.
그러나 exdends의 다중상속이 가능해 지게 된다면 컴파일러도, 프로그래머도 어떤 부모가 우선순위권인지, 어떠한 조합으로 상속을 받아야 할지 정말 복잡해지기 때문에 이를 사전에 막아 버린 것이다.
이 때문에 많은 프로그래밍 언어들은 하나의 클라스에서 두 개 이상의 클래스를 직접상속을 하지 못하게 막아 놓았다.
만약 우리가 날아다니는 자동차가 있다 해보자. 그러면 당연히 날아다니는 자동차는 비행기의 기능과 차량의 기능을 가지고 있어야 하기 때문에 하나의 자녀클래스가 두 부모의 상속을 받고 싶을 것이다. 그리고 그 두 부모의 클래스들은 '탈 것'의 기능들을 상속받고 싶어 할 것이다.
그러면 코드 베이스는 다음과 같이 작성된다
AbstractVehicle.java
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
abstract class AbstractVehicle {
@Getter
private final int passengerAmt;
@Getter
private final BigDecimal bodyFrameStrength;
@Setter
private int numberOfEntrance;
public AbstractVehicle(int passengerAmt, BigDecimal bodyFrameStrength) {
this.passengerAmt = passengerAmt;
this.bodyFrameStrength = bodyFrameStrength;
}
public int getNumberOfEntrance() {
if (numberOfEntrance == 0) {
System.out.println("Warning. No entrance found for this vehicle.");
}
return numberOfEntrance;
}
}
AbstractCar.java
import lombok.Getter;
import java.math.BigDecimal;
abstract class AbstractCar extends AbstractVehicle {
private final BigDecimal windowStrength;
@Getter
private final BigDecimal enginePower;
public AbstractCar(int passengerAmt,
BigDecimal frameStrength,
BigDecimal enginePower,
BigDecimal windowStrength) {
super(passengerAmt, frameStrength);
this.windowStrength = windowStrength;
this.enginePower = enginePower;
}
public BigDecimal getTotalFrameStrength() {
BigDecimal bodyFrameStrength = super.getBodyFrameStrength();
return bodyFrameStrength.add(windowStrength);
}
}
AbstractPlane.java
import lombok.Getter;
import java.math.BigDecimal;
abstract class AbstractPlane extends AbstractVehicle {
private final BigDecimal wingFrameStrength;
private final BigDecimal windowStrength;
@Getter
private final BigDecimal jetEnginePower;
public AbstractPlane(int passengerAmt,
BigDecimal frameStrength,
BigDecimal jetEnginePower,
BigDecimal wingFrameStrength,
BigDecimal windowStrength) {
super(passengerAmt, frameStrength);
this.wingFrameStrength = wingFrameStrength;
this.windowStrength = windowStrength;
this.jetEnginePower = jetEnginePower;
}
@Override
public void setNumberOfEntrance(int numberOfEntrance) {
if (numberOfEntrance > 2) {
throw new RuntimeException("Too many entrance for plane");
}
super.setNumberOfEntrance(numberOfEntrance);
}
public BigDecimal getTotalFrameStrength() {
BigDecimal bodyFrameStrength = super.getBodyFrameStrength();
return wingFrameStrength.add(bodyFrameStrength).add(wingFrameStrength).add(windowStrength);
}
}
FlyingCar.java
import java.math.BigDecimal;
public class FlyingCar extends AbstractPlane, AbstractCar { //Invalid extension
public FlyingCar(int passengerAmt,
BigDecimal frameStrength,
BigDecimal jetEnginePower,
BigDecimal wingFrameStrength,
BigDecimal windowStrength) {
//Invalid parent constructor init. Cannot perform twice, or does not know which parent to give
super(passengerAmt, frameStrength, jetEnginePower, wingFrameStrength, windowStrength);
//super(passengerAmt, frameStrength, jetEnginePower, wingFrameStrength, windowStrength);
}
}
그러나 위와 같이 코드를 작성하게 되면 컴파일러가 다음 부분에서 오류를 낼 것이다.
public class FlyingCar extends AbstractPlane, AbstractCar
다중 extends상속을 막아놓았기 때문이다.
그러기 때문에 보통 많은 언어들은 단일상속(extends)으로 제약을 둔다
그러나 인터페이스의 상속은 개수의 제한이 없기때문에 extension 단일상속으로 제한 우회 할 수 있을 거라 생각할 수 있을 것이다. 다음과 같이 말이다.
그러나 위와 같은 상속은 말이 안된다는 것을 알 수 있다.
인터페이스 클라스는 연산작업이나 인스턴스 변수 저장을 못하기 때문이다. 정말 다행이다.
그렇다면 default 메서드를 이용해서 인스턴스 변수저장은 못하더라도 연산이 가능하게 만들어서 Diamond Problem이 일어나게 만들면 어떤 일이 일어날까? 물론 우리가 위에서 원하는 방식구동은 하지 못한다. 그러나 연산 우선순위권이 어떻게 될까?
IGrandParent.java
interface IGrandParent {
default void opinion(String statement) {
System.out.println("I am grand parent. Let me " + statement);
}
}
IMother
public interface IMother extends IGrandParent{
default void opinion(String statement) {
System.out.println("I am mother. Let me " + statement);
IGrandParent.super.opinion("clean house");
}
}
IFather.java
public interface IFather extends IGrandParent{
default void opinion(String statement) {
System.out.println("I am father. Let me " + statement);
IGrandParent.super.opinion("cook");
}
}
Child.java
public class Child implements IFather, IMother{
public void opinion(String statement) {
System.out.println("I am a child. Let me " + statement);
}
}
위의 코드를 작성하고 Child 오브젝트의 opinion 함수를 호출하면 정상적으로 자식 의견만 출력이 된다.
I am a child. Let me do play outside
그런데 부모의 의견도 궁금해서 이렇게 작성해 본다.
public class Child implements IFather, IMother{
public void opinion(String statement) {
System.out.println("I am a child. Let me " + statement);
super.opinion("workout");
}
}
이렇게 작성하면 컴파일러는 IFather의 opinion 메서드를 실행 할 지, IMother의 opinion메서드를 실행할지 명확하지 않아서 컴파일 오류를 낸다.
때문에 다음과 같이 인터페이스 클라스를 직접 지정(Qualify)을 해줘야 정상적으로 출력된다.
public class Child implements IFather, IMother {
public void opinion(String statement) {
System.out.println("I am a child. Let me " + statement);
IFather.super.opinion("workout");
IMother.super.opinion("play tennis");
}
}
결국 모호한 함수호출이 없기 때문에 모두 정상 출력이 된다
I am a child. Let me play outside
I am father. Let me workout
I am grand parent. Let me cook
I am mother. Let me play tennis
I am grand parent. Let me clean house
'프로그래밍 기법' 카테고리의 다른 글
개발자의 필수 도구: 디자인 패턴, 왜 중요하고 어떻게 쓸까? 🛠️ (0) | 2025.07.05 |
---|---|
SOLID principle 이란? (0) | 2024.04.18 |
소프트웨어 디자인 패턴 - 데코레이터(DecoratorPattern - Structual Patterns - 1) (2) | 2024.02.26 |
소프트웨어 디자인 패턴 - 팩토리 패턴 (Factory Pattern - Creational Design Pattern - 4) (0) | 2024.01.30 |
소프트웨어 디자인 패턴 - Prototype Pattern (Creational Design Pattern - 3) (2) | 2024.01.14 |