코드 재사용을 목적으로 상속을 사용하면 변경하기 어렵고 유연하지 못한 설계에 이를 확률이 높아진다. 상속은 타입 계층을 구조화하기 위해 사용해야 한다.
<aside> ❓ Q. 여기서 중요한 것은 “타입 계층을 구조화하기 위해서”라고 했는데 타입계층 구조화는 어떻게 하는거고, 어떤 의미인거지?
A.
타입 계층을 구조화한다는 것은 객체 지향 프로그래밍에서 매우 중요한 개념입니다. 이는 클래스들 간의 관계를 명확하게 정의하고, 이를 통해 코드의 재사용성과 유지보수성을 높이는 방법을 의미합니다. 타입 계층 구조화는 주로 상속과 인터페이스를 통해 이루어집니다. 상속을 사용하면 하위 클래스가 상위 클래스의 속성과 메소드를 재사용할 수 있으며, 인터페이스를 통해서는 구현을 강제하여 다형성을 실현할 수 있습니다.
다형성은 같은 인터페이스나 기본 클래스를 공유하는 다양한 객체들이 그 인터페이스나 클래스에 정의된 메소드를 다른 방식으로 실행할 수 있도록 하는 객체 지향 프로그래밍의 특징입니다. 이를 통해 다양한 타입의 객체를 동일한 방식으로 처리할 수 있게 됩니다.
다음 예시에서는 타입 계층 구조화와 다형성을 보여주는 간단한 Java 코드를 살펴보겠습니다. 이 예시에서는 동물을 나타내는 기본 클래스 **Animal
**과 이를 상속받는 두 하위 클래스 **Dog
**와 **Cat
**을 정의합니다. 또한, 이들 모두가 speak
메소드를 다르게 구현함으로써 다형성을 실현합니다.
// 기본 클래스
class Animal {
void speak() {
System.out.println("This animal speaks in a way unique to its species.");
}
}
// Dog 클래스는 Animal 클래스를 상속받습니다.
class Dog extends Animal {
@Override
void speak() {
System.out.println("Woof");
}
}
// Cat 클래스는 Animal 클래스를 상속받습니다.
class Cat extends Animal {
@Override
void speak() {
System.out.println("Meow");
}
}
// 메인 클래스와 메인 메소드
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
// 동적 바인딩을 통해 실행 시점에 각 객체의 speak 메소드가 호출됩니다.
myDog.speak(); // 출력: Woof
myCat.speak(); // 출력: Meow
// 배열을 사용하여 다형성을 더 쉽게 볼 수 있습니다.
Animal[] animals = {new Dog(), new Cat()};
for(Animal animal : animals) {
animal.speak(); // 동적으로 올바른 메소드가 호출됩니다.
}
}
}
이 예시에서 Animal
클래스는 타입 계층의 최상위에 위치하며, **Dog
**와 Cat
클래스는 이를 상속받아 speak
메소드를 각자의 방식으로 구현합니다.
이를 통해, **Dog
**와 Cat
객체를 Animal
타입으로 처리할 수 있으며, 실행 시간에 Animal
참조를 통해 실제 객체의 speak
메소드가 호출됩니다.
이런 방식으로 타입 계층을 구조화하고 다형성을 활용하면, 코드의 재사용성과 유지보수성이 크게 향상됩니다.
</aside>
초기에 상속은 타입 계층과 다형성을 구현할 수 있는 거의 유일한 방법이었다. 이에 상속에 대한 맹신 추종이 자라났다. 하지만 여전히 상속은 다형성을 구현할 수 있는 가장 일반적인 방법이다.
하지만 상속 이외에도 다형성을 구현할 수 있는 다양한 방법들을 제공하고 있다.
그럼, 어떤 다양한 방법들이 있는 것일까??