상속(Inheritance)과 조합(Composition)은 객체지향 프로그래밍(OOP)에서 객체 간의 관계를 나타내는 데에 있어서 중요한 개념이다. 이번 글에서는 두 가지 개념의 차이점과 장단점에 대해서 알아보자.
상속(Inheritance)
상속은 객체지향 프로그래밍에서 가장 기본적인 개념 중 하나다. 클래스 간의 부모-자식 관계를 정의하여 부모 클래스의 속성과 메서드를 자식 클래스에서 재사용 할 수 있도록 한다.
이렇게 상속을 통해 구현한 클래스를 서브 클래스(Sub Class) 또는 파생 클래스(Derived Class)라고 부르기도 한다. 보통 상속을 사용하는 방법은 아래와 같다.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void introduce() {
System.out.println("My name is " + this.name + " and I am " + this.age + " years old.");
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String school) {
super(name, age);
this.school = school;
}
public String getSchool() {
return this.school;
}
@Override
public void introduce() {
System.out.println("My name is " + this.getName() + " and I am a student at " + this.school + ".");
}
}
위 코드에서 Student 클래스는 Person 클래스를 상속하고 있다. Student 클래스에서는 Person 클래스에서 정의한 name과 age 속성과 introduce 메서드를 상속 받아 사용할 수 있다.
이렇게 상속을 받으면 Student 클래스에서는 Person 클래스에서 구현한 일부 기능을 재사용할 수 있으며, Person 클래스에서 변경된 내용이 Student 클래스에도 반영된다.
메서드를 재정의한다는 의미는 마치 인터페이스와 유사한 기능을 제공하는 것 처럼 보이지만, 인터페이스는 추상 메서드만을 정의할 수 있으며, implements 받은 클래스에서는 반드시 해당 메서드를 구현해야 한다.
장점
- 코드의 재사용성이 높아진다. 부모 클래스에서 구현한 속성과 메소드를 자식 클래스에서 그대로 사용할 수 있다.
- 코드의 가독성이 높아진다. 부모 클래스와 자식 클래스 간의 관계가 명확해져 코드를 이해하기 쉬워진다.
단점
- 클래스 간의 결합도가 높아진다. 부모 클래스가 수정되면 자식 클래스에도 영향을 미치기 때문에, 클래스 간의 관계가 복잡해질수록 유지보수가 어려워진다.
- 클래스의 계층 구조가 복잡해질수록 코드의 이해와 디버깅이 어려워진다.
조합(Composition)
기존 클래스가 새로운 클래스의 구성요소로 쓰인다. 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조한다. 위에서 살펴봤던 상속 관계의 Person, Student 클래스들을 조합으로 사용한 예는 다음과 같다.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public void introduce() {
System.out.println("My name is " + this.name + " and I am " + this.age + " years old.");
}
}
public class Student {
private String school;
private final Person person;
public Student(String school, Person person) {
this.school = school;
this.person = person;
}
public String getSchool() {
return this.school;
}
public void introduce() {
person.introduce();
}
}
이처럼 Student 클래스가 인스턴스 변수로 Person 클래스로 가지는 것이 조합(Composition)이라고 하는데, 인스턴스 변수인 person을 사용하여 Person 클래스의 메서드를 호출하는 방식으로 동작한다.
장점
- 클래스 간의 결합도를 낮출 수 있다. 클래스 내에서 다른 클래스의 인스턴스를 사용하기 때문에, 클래스 간의 관계가 단순화된다.
- 유연성이 높아진다. 다른 클래스의 인스턴스를 포함시켜 사용하기 때문에, 클래스 간의 관계를 동적으로 변경할 수 있다.
- 클래스의 계층 구조가 복잡하지 않아 코드의 이해와 디버깅이 쉽다.
단점
- 코드의 일관성을 유지하기 어렵다.
결론
캡슐화를 깨뜨리고 상위 클래스에 의존하게 되어서 객체의 변화에 유연하지 못한 상속을 사용하기 보다는 조합을 사용하자. 하지만 조합이 무조건 좋은건 아니다.
상속이 적절하게만 사용된다면 더 강력하고 유지보수성이 증대될 수 있다. 단, 상속이 적절하게 사용되려면 다음과 같은 조건을 만족해야 한다.
확장과 설계를 고려한 is - a 관계일 때
is - a 관계라는 것은 ~은 ~이다 관계이다. 예를 들면 Tiger is a animal과 같은 관계이다. 호랑이가 동물인 사실은 변할 가능성이 없는 관계이다. 이렇게 확실한 is - a 관계일 때 상속의 이점이 극대화 된다.
상위 클래스의 메서드가 결함이 있을 때 하위 클래스까지 전파해도 괜찮은 경우
이 경우는 앞서 설명한 is - a 관계와 직접적인 관계가 있다. 동물과 호랑이 모두 숨을 쉬는 객체다. 이렇게 당연하다는 듯이 하위 클래스로 전파해도 되는 메서드가 존재할 때 상속을 적절하게 사용할 수 있다.
상속을 코드 재사용 용도로만 사용하지 말고 확장이라는 관점에서 사용해야 하며, 상황에 맞는 최선의 방법을 선택해야 한다. 위 2가지 상황을 만족한다면 상속을 사용해도 좋지만, 애매한 상황에서는 조합을 사용하는 것이 좋을 때가 더 많다.
'Java' 카테고리의 다른 글
[Test] Mockito 사용법 (2) | 2023.04.17 |
---|---|
[Java] DTO를 잘 사용하기 위한 고찰 (4) | 2023.03.28 |
[Java] 개행(줄 바꿈)시 lineSeparator를 써야하는 이유 (1) | 2023.03.09 |
[Java] 싱글톤 패턴은 안티 패턴일까? (2) | 2023.03.09 |
[Java] 정적 팩토리 메서드 사용기 (6) | 2023.03.03 |