6 분 소요

다형성이란?

  • 상속을 통해 부모 클래스의 기능을 그대로 사용할 수 있으며, 필요한 경우 오버라이딩으로 기능을 자식 클래스에 맞게 수정할 수 있다.
  • 인터페이스와의 차이점은, 상속은 부모 클래스의 구현을 그대로 물려받기 때문에 중복 구현 없이 공통 기능을 사용할 수 있다는 점이다.
  • 핵심은 “부모 타입 하나로 여러 자식 객체를 다룰 수 있다”는 것. 이를 통해 코드를 유연하고 확장 가능하게 만들 수 있다.
  • 단, 다형성의 구체적인 구현 방식과 제약은 언어마다 차이가 있을 수 있다
항목 Java Python TypeScript
정적/동적 타이핑 정적 타입 동적 타입 정적 타입 (옵셔널)
상속 문법 extends (단일 상속) class C(A, B) (다중 상속 가능) extends (단일 상속)
인터페이스 지원 명시적 (interface) 약함 (abc 모듈, duck typing 중심) 명시적 (interface, implements)
다중 상속 ❌ 지원하지 않음 ✅ 기본 지원 ❌ 지원하지 않음 (Mixin으로 우회 가능)
덕 타이핑 ❌ 불가 ✅ 지원 (형보다 행동 중심) ⚠️ 부분 지원 (구조적 타이핑)
다형성 활용 방식 부모 타입 참조 덕 타이핑 또는 부모 참조 부모 타입 또는 인터페이스 참조
메서드 오버로딩 ✅ 지원 (정적 시그니처 기반) ❌ 미지원 (가변 인자 등으로 대응) ⚠️ 부분 지원 (시그니처 + 조건 분기)
추상 클래 지원 ✅ (abstract class) ✅ (abc 모듈) ✅ (abstract class)

덕 타이핑

class Dog:
    def speak(self):
        print("멍멍!")

class Cat:
    def speak(self):
        print("야옹!")

def make_sound(animal):
    animal.speak()  # animal이 speak 메서드만 갖고 있으면 됨

make_sound(Dog())  # 멍멍!
make_sound(Cat())  # 야옹!

  • 덕 타이핑은 “타입이 아니라 행동이 중요하다”는 철학
  • Python, JavaScript 등 동적 타이핑 언어에서 자주 사용됨
  • 장점: 유연하고 간단함
  • 단점: 실수해도 오류를 컴파일 시점에 못 잡고 런타임에 알게 될 수 있음

특이사항

  • Python은 class C(A, B): 형태로 여러 부모 클래스를 동시에 상속받을 수 있다.
  • 메서드 충돌 시 MRO(Method Resolution Order) 규칙에 따라 우선순위를 결정한다.
  • Java와 TypeScript는 클래스 단위 다중 상속은 금지되며, 대신 인터페이스를 조합하여 유사한 구조를 만든다.

비교차트

Java

다형성 X (Java)

class Dog {
    public void breathe() {
        System.out.println("숨 쉬는 중...");
    }
    public void sound() {
        System.out.println("멍멍!!");
    }
}

class Cat {
    public void breathe() {
        System.out.println("숨 쉬는 중...");
    }
    public void sound() {
        System.out.println("야옹");
    }
}

public class Zoo {
    private final Dog dog;
    private final Cat cat;

    public Zoo(Dog dog, Cat cat) {
        this.dog = dog;
        this.cat = cat;
    }

    public void startShow() {
        dog.breathe();
        dog.sound();
        cat.breathe();
        cat.sound();
    }

    public static void main(String[] args) {
        Zoo zoo = new Zoo(new Dog(), new Cat());
        zoo.startShow();
    }
}
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

기존의 클래스를 수정할 때 Zoo 클래스도 같이 수정해야하며 duck 이라는 클라스를 추가하려고 할 때도 생성자부터 구현할 때 굉장히 귀찮아짐 결합도가 높다 -> 유지보수 힘들어짐

다형성 O (Java)

import java.util.*;
  
abstract class Animal {
    public void breathe() {
        System.out.println("숨 쉬는 중...");
    }
    public abstract void sound();
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("멍멍!");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("야옹~");
    }
}

public class P_Zoo {
    private final List<Animal> animals;
    public P_Zoo(List<Animal> animals) {
        this.animals = animals;
    }

    public void startShow() {
        for (Animal animal : animals) {
            animal.breathe(); // 공통 기능
            animal.sound();   // 자식마다 다르게 동작 (다형성!)
        }
    }

    public static void main(String[] args) {

        List<Animal> list = new ArrayList<>();
        list.add(new Dog());
        list.add(new Cat());
        P_Zoo zoo = new P_Zoo(list);
        zoo.startShow();
    }
}
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

위와 같이 구현하면 동일 메서드의 중복코드는 줄어들고 새로 추가할 때도 List<Animal> list = new ArrayList<>(); 여기에만 추가하면 끝

Python

다형성 X (Python)

class Dog:

    def breathe(self):
        print("숨 쉬는 중...")

    def sound(self):
        print("멍멍!!")  

class Cat:

    def breathe(self):
        print("숨 쉬는 중...")

    def sound(self):
        print("야옹")

class Zoo:

    def __init__(self, dog, cat):
        self.dog = dog
        self.cat = cat

    def start_show(self):
        self.dog.breathe()
        self.dog.sound()
        self.cat.breathe()
        self.cat.sound()

if __name__ == "__main__":
    zoo = Zoo(Dog(), Cat())
    zoo.start_show()
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

다형성 O (Python)

from abc import ABC, abstractmethod

class Animal(ABC):

    def breathe(self):
        print("숨 쉬는 중...")

    @abstractmethod
    def sound(self):
        pass


class Dog(Animal):

    def sound(self):
        print("멍멍!")

class Cat(Animal):

    def sound(self):
        print("야옹~")


class Zoo:

    def __init__(self, animals):
        self.animals = animals

    def start_show(self):
        for animal in self.animals:
            animal.breathe()
            animal.sound()

  
if __name__ == "__main__":
    animals = [Dog(), Cat()]
    zoo = Zoo(animals)
    zoo.start_show()
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

TypeScript

다형성 X (TypeScript)

class Dog {
    constructor() {}
    breathe(): void {
        console.log("숨 쉬는 중...");
    }

    sound(): void {
        console.log("멍멍!!");
    }
}

class Cat {
    constructor() {}
    breathe(): void {
        console.log("숨 쉬는 중...");
    }
    
    sound(): void {
        console.log("야옹~");
    }
}

class Zoo {
    constructor(private dog: Dog, private cat: Cat) {}

    startShow(): void {
        this.dog.breathe();
        this.dog.sound();
        this.cat.breathe();
        this.cat.sound();
    }
}

// 실행
const dog = new Dog();
const cat = new Cat();
const zoo = new Zoo(dog, cat);

zoo.startShow();
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

다형성 O (TypeScript)

// Animal 인터페이스: 공통 동작 정의
interface Animal_ {
    breathe(): void;
    sound(): void;
}

// Dog 클래스
class Dog_ implements Animal_ {
    constructor() {}
    breathe(): void {
        console.log("숨 쉬는 중...");
    }
    
    sound(): void {
        console.log("멍멍!");
    }
}

// Cat 클래스
class Cat_ implements Animal_ {
    constructor() {}
    breathe(): void {
        console.log("숨 쉬는 중...");
    }
    
    sound(): void {
        console.log("야옹~");
    }
}

// Zoo 클래스
class Zoo_ {
    constructor(private animals: Animal_[]) {}
    startShow(): void {
        for (const animal of this.animals) {
            animal.breathe();
            animal.sound();
        }
    }
}

// 실행
const animals: Animal_[] = [
    new Dog_(),
    new Cat_()
];

const zoo_ = new Zoo_(animals);
zoo_.startShow();
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~

카테고리:

업데이트:

댓글남기기