Java

[Java] 상속(Inheritance) & 조합 (Composition)

DH_0518 2024. 4. 10. 15:50

Java와 같은 객체 지향 프로그래밍 언어에서는 상속(Inheritance)이라는 중요한 개념이 존재한다. 상속과 조합을 비교하기 전, 먼저 상속에 대해 알아보자

 

 

 

Inheritance

 

 

상속이란 부모 클래스(Super Class)의 특성을 자식 클래스(Sub Class)가 물려받아 사용할 수 있도록 해주는 메커니즘이다. Java에서는 extends 키워드를 사용하여 자식 클래스가 부모 클래스를 상속할 수 있다. 상속의 특징을 통해 장단점과 사용 목적을 알아보자

 

Java의 상속 (Inheritance)

  • 코드 재사용성
    : 상속을 통해 동일한 속성을 가진 객체들이, 매번 동일한 필드와 메서드를 작성할 필요 없이 '부모 클래스'에 선언된 필드와 메서드를 그대로 사용하게 함으로써 코드 재사용성을 높여준다
  • 확장 가능성
    : '부모 클래스'의 필드나 메서드를 변경하지 않고서도 '자식 클래스'의 고유한 필드나 메서드를 선언하여 기능을 확장할 수 있다
    단, 코드 재사용만을 위해서 상속을 사용한다 생각하면 안된다. 엄연히 상위 클래스를 하위 클래스를 통해 좀 더 구체적으로 구현하기 위해 사용되는게 상속이라는 것을 명심해야한다
  • 메서드 오버라이딩
    : 반대로 '자식 클래스'에서 '부모 클래스'의 메서드를 그대로 사용하지 않고 재정의 함으로써, '부모 클래스'의 메서드를 바꾸지 않고서도 '자식 클래스'에서 원하는대로 내부 동작을 변경할 수 있다
  • 단일 상속
    : 다중 상속을 지원하지 않으므로, '자식 클래스'는 단 하나의 '부모 클래스'만을 상속할 수 있다

 

 

 

이처럼 상속은 코드의 재사용성과 확정성을 높이고 프로그램의 구조를 계층화하여 유지보수성을 높이는 등의 장점이 있다. 하지만 적절하게 사용하지 않으면 클래스 간의 의존성이 높아져 유연성이 떨어지는 단점도 존재한다

 

상속의 단점

  • 강한 결합성
    : '부모 클래스'와 '자식 클래스'가 강하게 결합되기에, '자식 클래스'는 '부모 클래스'의 내부 구현에 의존하게 된다. 예를들어 '부모 클래스'의 메서드 시그니처가 변경된다면 '자식 클래스'에서의 변경 또한 필수가 된다
  • 불필요한 상속
    : 상속을 통해 '부모 클래스'의 모든 기능이 '자식 클래스'로 상속되므로, 불필요한 메서드나 필드도 상속될 수 있다
  • 다중 상속 불가능
    : '자식 클래스'가 하나의 '부모 클래스'를 상속하고 있다면, 추가적으로 상속할 수 없다. 여러 '부모 클래스'에서 동일한 메서드 시그니처를 가진다면 어떤 '부모 클래스'의 메서드를 상속받을지 알 수 없기 때문이다(인터페이스는 가능하다)
  • 유연하지 못한 설계
    : 컴파일시에 구성 요소들이 정해지므로, 구성 요소를 동적으로 변경할 수 없다
// 인터페이스의 다중 상속
public interface Father{
   void love();
}

public interface Mother{
   void love();
}

public class Child implements Father, Mother {
   // 어떤 인터페이스의 메서드를 상속받았는지 전혀 상관이 없다
   @Override
   void love(){
      System.out.println("L.O.V.E");
   }
  
   public static void main(){
      Child child = new Child();
      child.love();  // "L.O.V.E"
   }
}

 

 

 

 

Composition

 

 

그렇다면 위에서 말한 상속의 단점들을 어떻게 조합으로 해결할 수 있는지, 컴포지션의 특징을 통해 알아보자

 

Composition

  • has - a 관계
    : 클래스가 다른 객체의 인스턴스를 포함하는 개념으로, 특정 객체의 인스턴스(구현체)를 자신의 인스턴스 변수(필드)로 선언한 후, 그 인스턴스의 필드나 메서드 등을 사용하는 기법이다. 
  • 수평적 구조
    : 상속이 'is-a' 관계를 통해 수직적 구조를 가져간다면, 조합은 'has-a'관계를 통해 수평적 구조를 가져간다. 따라서 (Student, Subjcet), (Car, Engine)처럼 아주 연관이 없지는 않지만 상속 관계로 맺기에는 애매한 것들을 다룰 수 있다
  • 낮은 결합도
    : 포함한 인스턴스의 내부 구현이 바뀌더라도, 자신(인스턴스 변수를 선언한 클래스)은 영향을 받지 않는다
  • 선택적 상속
    : 포함한 인스턴스의 모든 메서드 및 필드를 상속받아 사용하는 것이 아니라, 내가 원하는 기능만을 사용할 수 있으므로 불필요한 기능을 상속하지 않아도 된다
  • 유연한 설계
    : 필드로 포함한 인스턴스가 interface의 구현체가 될 수도 있다. 따라서 자신의 인스턴스 변수로 interface를 선언했을 때, 그 interface를 구현한 구현체라면 어떤 객체라도 자신의 필드로 포함시킬 수 있다. 따라서 runtime시에 동적으로 객체의 타입을 변경할 수 있다
  • 다중 상속
    : 몇 개의 클래스가 됐든, 그 클래스들의  인스턴스를 필드로 포함시키기만 한다면 각각의 클래스들의 기능을 모두 사용할 수 있다

이처럼 반드시 어떤 특정한 관계일 때만 상속을 사용하고, 그 외의 경우에는 조합을 적극 사용하자

 

 

 

상속 예시

// 상속
public abstract class Developer {
	private final String name;
	public Developer(String name) {
		this.name = name;
	}
	public void talk() {
		System.out.println("I am " + name);
	}
	abstract void create();
}


public class Backend extends Developer {
	public Backend(String name) {
		super(name);
	}
	@Override
	public void create() {
		System.out.println("create api");
	}
}


public class Frontend extends Developer {
    public Frontend(String name) {
        super(name);
    }
    @Override
    public void create() {
        System.out.println("create ux/ui");
    }
}

 

조합 예시

public class Developer_composition {

    private final String name;
    private final Role role; // role 객체를 인스턴스 변수로 포함시킨다

    public Developer_composition(String name, Role role) {
        this.name = name;
        this.role = role;
    }
    public void talk() {
        System.out.println("I am " + name);
    }
    // role의 메서드를 그대로 사용한다
    public void create() {
        role.create();
    }
}


public class BE_composition implements Role {
	@Override
	public void create() {
		System.out.println("create api");
	}
}


public class FE_composition implements Role {
	@Override
	public void create() {
		System.out.println("create ui");
	}
}

 

생성 방법 및 사용 결과

public static void main(String[] args) {
    // inheritance
    Backend backend = new Backend("Backend Developer");
    Frontend frontend = new Frontend("Frontend Developer");
    backend.talk();
    backend.create();
    frontend.talk();
    frontend.create();
    
    
    System.out.println("=====================================");
    // composition
    Developer_composition BE_composition = new Developer_composition("Backend Developer", new BE_composition());
    Developer_composition FE_composition = new Developer_composition("Frontend Developer", new FE_composition());
    BE_composition.talk();
    BE_composition.create();
    FE_composition.talk();
    FE_composition.create();
}

 

 

 

 

 

 

 

 

 

Reference

 

[Java] 컴포지션(Composition) | 👨🏻‍💻 Tech Interview

[Java] 컴포지션(Composition) 우선 상속(Inheritance)이란, 하위 클래스가 상위 클래스의 특성을 재정의 한 것을 말한다. 부모 클래스의 메서드를 오버라이딩하여 자식에 맞게 재사용하는 등, 상당히 많

gyoogle.dev

 

[Java] 상속(inheritance)과 컴포지션(composition)에 대해서

느슨하게 결합된 코드는 더 많은 유연셩을 제공하기 때문에 상속보다는 컴포지션을 사용하는 것을 권장함. effective java에서도 상속보다는 컴포지션을 사용하기를 권장한다.(item 18) 하지만 권장

velog.io

 

[JAVA] 컴포지션(Composition): 조합이 뭘까? 컴포지션의 개념/사용, 상속을 사용한 코드를 조합을 사

💋 인트로 우아한테크코스 미션 중 블랙잭 미션을 진행할 때, 굉장히 핫한 주제가 있었다. 바로 상속과 조합이었다. 조합에 대해 처음 들어보았고, 찾아보려 검색했지만 이펙티브 자바에서 다

engineerinsight.tistory.com

 

상속(Inheritance) vs 컴포지션(Composition)

Content

iyoungman.github.io

 

[Java] 자바의 다중상속 (feat. 인터페이스와 추상클래스)

알고 시작해야 할 내용 1. 자바에서 클래스의 다중 상속은 불가능하다. 2. 하지만 인터페이스의 다중 상속은 가능하다. why? 인터페이스와 추상클래스를 공부하던 중, 자바의 다중상속 이란 개념

junior-datalist.tistory.com