목차
1. 같은 attack()이 다르게 동작하는 이유
지난 시간에 만든 RPG 게임을 운영하고 있다고 가정해보겠습니다. 캐릭터 목록에는 전사, 마법사, 궁수, 성기사, 암살자가 있습니다.
전사
마법사
궁수
성기사
암살자게임 서버는 매우 단순하게 공격 명령을 호출합니다.
character.attack()그런데 운영팀이 이상한 제보를 합니다. 같은 attack()인데 캐릭터마다 결과가 다르게 보입니다.
| 캐릭터 | 공격 결과 |
|---|---|
| 전사 | 검 공격! |
| 마법사 | 파이어볼! |
| 궁수 | 화살 발사! |
| 암살자 | 그림자 베기! |
같은 attack()인데 왜 다르게 동작할까요? 이것은 버그가 아니라 객체지향 프로그래밍의 핵심 원리인 다형성 Polymorphism입니다.
2. 다형성의 의미
Polymorphism이라는 영어 단어를 나누어 보면 Poly는 많은, Morph는 형태를 의미합니다. 즉 다형성은 많은 형태라는 뜻입니다.
| 단어 | 의미 |
|---|---|
| Poly | 많은 |
| Morph | 형태 |
| Polymorphism | 많은 형태 |
프로그래밍에서는 같은 명령어인데 상황에 따라 다르게 동작하는 능력을 의미합니다.
현실 세계의 다형성
문을 열어라라는 같은 명령을 내려도 문 종류에 따라 동작이 달라집니다.
| 대상 | 문 열기 동작 |
|---|---|
| 자동문 | 스르륵 열림 |
| 미닫이문 | 좌우로 이동 |
| 회전문 | 빙글빙글 회전 |
명령은 같지만 실제 동작 방식은 다릅니다. 이것이 다형성입니다.
3. 상속과 오버라이딩 연결
지난 시간에는 상속과 오버라이딩을 배웠습니다. 다형성은 이 흐름 위에서 자연스럽게 등장합니다.
class Character:
def attack(self):
print("기본 공격")class Warrior(Character):
def attack(self):
print("검 공격")class Wizard(Character):
def attack(self):
print("파이어볼")class Archer(Character):
def attack(self):
print("화살 발사")각 클래스는 같은 attack()이라는 이름을 사용하지만 내부 동작은 서로 다릅니다.
4. 전투 시스템 예제
운영팀이 전투 시스템을 만들어달라고 요청했습니다. 요구사항은 단순합니다. 캐릭터가 공격한다는 것입니다.
warrior.attack()
wizard.attack()
archer.attack()결과입니다.
검 공격
파이어볼
화살 발사여기서 중요한 사실은 전투 시스템이 상대가 누구인지 몰라도 된다는 점입니다.
character.attack()각 캐릭터가 알아서 다른 행동을 합니다. 이것이 객체지향의 강력한 지점입니다.
자동 전투 시스템
def battle(character):
character.attack()사용합니다.
battle(warrior)
battle(wizard)
battle(archer)결과입니다.
검 공격
파이어볼
화살 발사battle 함수는 캐릭터 종류를 전혀 모릅니다. 그런데도 각 객체가 가진 attack() 구현에 따라 정확히 동작합니다.
새 캐릭터가 추가된다면
신규 캐릭터 드래곤 슬레이어가 추가되어도 battle 함수는 수정할 필요가 없습니다.
class DragonSlayer(Character):
def attack(self):
print("용살검")battle(dragon_slayer)결과입니다.
용살검기존 시스템을 수정하지 않고 새 객체를 추가할 수 있다는 점이 다형성이 제공하는 큰 장점입니다.
5. 동물원 프로젝트
게임 말고 동물원 예제로도 다형성을 이해할 수 있습니다.
강아지
고양이
오리부모 클래스입니다.
class Animal:
def sound(self):
pass강아지입니다.
class Dog(Animal):
def sound(self):
print("멍멍")고양이입니다.
class Cat(Animal):
def sound(self):
print("야옹")오리입니다.
class Duck(Animal):
def sound(self):
print("꽥꽥")동물원 방송 시스템입니다.
def make_sound(animal):
animal.sound()실행합니다.
make_sound(Dog())
make_sound(Cat())
make_sound(Duck())결과입니다.
멍멍
야옹
꽥꽥방송 시스템은 동물 종류를 전혀 모르지만 정확히 동작합니다.
6. Python 안의 다형성
Python은 원래 다형성을 좋아합니다. 우리는 이미 print()를 통해 다형성을 사용하고 있었습니다.
print(100)print("Python")print([1, 2, 3])같은 print()이지만 숫자, 문자열, List를 각각 알맞게 출력합니다. 내부적으로는 대상 객체의 형태에 맞게 다르게 처리됩니다.
어벤져스 비유
공격해라는 같은 명령을 내려도 히어로마다 행동이 다릅니다.
| 대상 | 공격 방식 |
|---|---|
| 헐크 | 주먹 |
| 토르 | 망치 |
| 아이언맨 | 레이저 |
| 스파이더맨 | 거미줄 |
7. 실무와 AI 시스템의 다형성
결제 시스템
전자상거래 서비스에서도 다형성은 자주 사용됩니다.
payment.pay()| 결제 수단 | pay() 동작 |
|---|---|
| 신용카드 | 카드 승인 |
| 계좌이체 | 계좌 출금 |
| 카카오페이 | 간편 결제 |
결제 함수는 동일하지만 실제 동작은 결제 수단 객체에 따라 달라집니다.
AI 시스템
AI 모델에서도 같은 구조를 볼 수 있습니다.
model.predict()| 모델 종류 | predict() 결과 |
|---|---|
| GPT 모델 | 텍스트 생성 |
| 이미지 모델 | 이미지 분류 |
| 음성 모델 | 음성 인식 |
같은 predict() 호출이 모델 객체에 따라 다른 결과를 만듭니다. AI 프레임워크도 다형성을 적극 활용합니다.
8. if 문 지옥을 줄이는 설계
다형성은 시니어 개발자의 비밀 무기입니다. 주니어 개발자는 객체 종류를 if 문으로 계속 확인하려고 할 수 있습니다.
if type == "warrior":if type == "wizard":if type == "archer":캐릭터가 늘어날수록 if 문이 계속 증가합니다.
다형성을 사용하면 다음 한 줄로 충분합니다.
character.attack()새 캐릭터가 추가될 때마다 조건문을 수정해야 합니다.
새 객체가 자기 행동을 구현하면 기존 전투 시스템은 수정하지 않아도 됩니다.
9. 객체지향 핵심 연결
다형성을 이해하는 순간 객체지향의 핵심 개념들이 연결됩니다.
| 개념 | 의미 |
|---|---|
| 상속 | 기능 물려받기 |
| 오버라이딩 | 기능 재정의 |
| 다형성 | 같은 명령, 다른 동작 |
게임 개발자가 되었다면 전사, 마법사, 궁수, 암살자, 성기사 같은 수십 개 캐릭터를 하나의 전투 시스템으로 관리할 수 있습니다. 실제 MMORPG 서버도 비슷한 구조를 사용합니다.
10. 정리와 다음 편 예고
오늘 배운 내용을 한 문장으로 정리하면 다형성 Polymorphism은 같은 명령어를 내려도 객체가 스스로 적절한 행동을 선택하는 능력입니다.
- 다형성은 같은 명령이 객체에 따라 다르게 동작하는 능력입니다.
- 상속과 오버라이딩을 이해하면 다형성을 자연스럽게 이해할 수 있습니다.
- battle(character)는 캐릭터 종류를 몰라도 character.attack()만 호출하면 됩니다.
- 새 객체가 추가되어도 기존 시스템을 수정하지 않는 확장 가능한 구조를 만들 수 있습니다.
- print(), payment.pay(), model.predict() 같은 코드에서도 다형성을 볼 수 있습니다.
- 다형성은 if 문 지옥을 줄이고 객체가 자기 행동을 책임지게 만듭니다.
| 객체지향 개념 | 역할 |
|---|---|
| Class | 객체를 만든다 |
| Inheritance | 능력을 물려준다 |
| Polymorphism | 같은 명령으로 다르게 행동한다 |
여기까지 이해했다면 초급 Python 문법을 넘어 객체지향 프로그래밍의 핵심 원리를 이해한 것입니다.
- 모듈 Module과 패키지 Package 이해하기
- 수천 개의 코드를 체계적으로 관리하는 방법
- 게임, 쇼핑몰, AI 프로젝트가 커질 때 개발자들이 코드를 정리하는 방식
