[Daily] 다형성 Java vs Python vs TypeScript 비교
다형성이란?
- 상속을 통해 부모 클래스의 기능을 그대로 사용할 수 있으며, 필요한 경우 오버라이딩으로 기능을 자식 클래스에 맞게 수정할 수 있다.
- 인터페이스와의 차이점은, 상속은 부모 클래스의 구현을 그대로 물려받기 때문에 중복 구현 없이 공통 기능을 사용할 수 있다는 점이다.
- 핵심은 “부모 타입 하나로 여러 자식 객체를 다룰 수 있다”는 것. 이를 통해 코드를 유연하고 확장 가능하게 만들 수 있다.
- 단, 다형성의 구체적인 구현 방식과 제약은 언어마다 차이가 있을 수 있다
항목 | 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();
숨 쉬는 중...
멍멍!
숨 쉬는 중...
야옹~
댓글남기기