객체 지향 프로그래밍(OOP)의 모든 것
앞서 고급 함수 기법을 통해 코드의 유연성을 높이는 방법을 살펴보았습니다. 이번 글에서는 데이터를 객체로 모델링하고 객체 간의 상호작용을 통해 프로그램을 설계하는 객체 지향 프로그래밍(OOP)의 핵심 개념과 4대 요소, 그리고 파이썬 특유의 구현 방식을 완벽하게 파헤쳐 보겠습니다.
1. 객체 지향 프로그래밍(OOP)이란?
객체 지향 프로그래밍(OOP)은 프로그램을 명령어의 집합이 아닌, 독립된 단위인 ‘객체(Object)’들의 모임으로 파악하고자 하는 프로그래밍 패러다임입니다. 데이터를 객체로 모델링하고 객체들 간의 상호작용을 통해 프로그램을 설계합니다.
OOP의 주요 장점
-
프로그램에 명확한 구조를 제공합니다.
-
코드 유지 관리, 재사용 및 디버깅이 더 쉬워집니다.
-
코드를 DRY (Don’t Repeat Yourself) 상태로 유지하는 데 도움을 줍니다.
-
더 적은 코드로 재사용 가능한 애플리케이션을 구축할 수 있습니다.
2. 클래스(Class)와 객체·인스턴스(Object & Instance)
-
클래스(Class): 객체를 생성하기 위한 설계도, 틀(Template) 또는 정의입니다. 객체가 가져야 할 속성(데이터)과 행동(메소드)을 정의합니다.
-
객체(Object) / 인스턴스(Instance): 클래스에 따라 메모리에 실제로 만들어진 실체입니다.
class Car:
# 클래스 변수 (모든 객체가 공유)
wheel = 4
# 생성자: 객체가 생성될 때 초기 속성을 설정하는 메소드
def __init__(self, color, model):
# 인스턴스 변수 (객체마다 독립적인 값)
self.color = color
self.model = model
# 인스턴스 메소드 (객체의 동작)
def drive(self):
print(f"{self.color} {self.model}이(가) 운전 중입니다.")
# Car 클래스의 객체(인스턴스) 생성 및 활용
my_car = Car("빨간색", "테슬라")
my_car.drive() # 출력: 빨간색 테슬라가 운전 중입니다.
2.1 타 언어(Java, C++)와의 클래스 비교
파이썬은 간결한 문법을 제공하지만, 자바(Java)나 C++ 같은 정적 타입 언어는 명시적인 타입 선언과 메모리 관리 방식(C++의 new/delete 등)을 사용합니다. 자바는 static을 통해 클래스 변수를 정의하며, C++은 멤버 초기화 리스트와 포인터 개념을 적극 활용합니다.
2.2 클래스 속성과 인스턴스 속성
-
클래스 속성: 클래스 전체에서 모든 인스턴스가 공유하는 변수입니다.
-
인스턴스 속성: 각각의 객체마다 독립적인 값을 가지는 변수입니다 (
self.속성명).
3. 파이썬 OOP의 특수 메서드 (매직 메서드 / 던더 메서드)
파이썬 클래스에는 양쪽에 언더바 두 개(__)가 붙는 매직 메서드(Magic Methods) 또는 던더 메서드(Dunder Methods)가 존재합니다. 객체의 기본적인 연산이나 표현 방식을 정의합니다.
-
__init__: 객체가 생성될 때 호출되는 생성자 메서드입니다. -
__str__:print()함수나str()로 객체를 문자열로 변환할 때 사용하는 사용자 친화적인 표현입니다. -
__repr__: 객체를 디버깅하거나 개발자에게 보여주기 위한 공식적이고 명확한 표현입니다. -
__del__: 객체가 파괴될 때 호출되는 소멸자입니다.
4. 생성자(__init__)와 소멸자(__del__)
-
생성자(
__init__): 객체가 생성될 때 자동으로 호출되어 초기 속성값을 설정합니다. 첫 번째 인자로는 언제나 자기 자신을 가리키는self가 전달됩니다. -
소멸자(
__del__): 객체가 메모리에서 해제될 때 호출됩니다. 파이썬은 자동 메모리 관리(Garbage Collection)를 지원하므로 소멸자를 직접 구현해야 하는 경우는 드뭅니다. 자원 해제는 보통with구문과 컨텍스트 매니저를 활용하는 것이 권장됩니다.
class Person:
def __init__(self, name):
self.name = name
print(f"{self.name} 객체가 생성되었습니다.")
def __del__(self):
print(f"{self.name} 객체가 소멸되었습니다.")
person = Person("Alice")
del person # 명시적 객체 삭제
5. OOP 핵심 4대 요소
5.1 캡슐화 (Encapsulation)와 @property
캡슐화는 데이터를 외부에서 직접 접근하지 못하게 보호하고 내부 메서드를 통해서만 제어하도록 만드는 기법입니다.
-
_변수명: 내부적으로만 사용하라는 암묵적 표기 (직접 접근 자제) -
__변수명: 네임 맹글링(Name Mangling)이 적용되어 외부에서 직접 접근 불가
@property 데코레이터
메소드를 마치 일반 속성처럼 접근할 수 있게 해주며, 데이터의 유효성 검사(Setter)와 결합하여 안전한 캡슐화를 구현합니다.
class BankAccount:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, amount):
if amount < 0:
raise ValueError("잔액은 음수가 될 수 없습니다!")
self._balance = amount
5.2 상속 (Inheritance)과 super()
기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받아 코드를 재사용하고 확장하는 기법입니다. 부모의 기능을 그대로 쓰거나 오버라이딩(재정의)할 수 있으며, super()를 통해 부모의 메서드를 명시적으로 호출할 수 있습니다.
class Animal:
def __init__(self, name):
self.name = name
def walk(self):
print("걷고 있습니다.")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 부모 생성자 호출
self.breed = breed
def walk(self):
print(f"{self.name}가 네 발로 달립니다.") # 오버라이딩
5.3 다형성 (Polymorphism)과 덕 타이핑 (Duck Typing)
다형성은 동일한 인터페이스나 메서드 이름이 서로 다른 객체에 대해 각기 다르게 동작하는 성질입니다. 파이썬은 동적 타입 언어로서 객체의 실제 타입보다 객체가 가진 행동(메소드)에 의해 적합성을 판단하는 덕 타이핑(Duck Typing) 철학을 따릅니다. (“오리처럼 걷고 꽥꽥거린다면 오리로 본다”) 이와 더불어 파이썬은 연산자 오버로딩(+ 연산자가 숫자 덧셈, 문자열 연결, 리스트 병합으로 다르게 동작하는 것)도 다형성의 일종으로 지원합니다.
5.4 추상화 (Abstraction)
복잡한 내부 구현은 숨기고 꼭 필요한 인터페이스만 제공하는 기법입니다. 파이썬에서는 abc 모듈의 ABC와 @abstractmethod를 사용하여 자식 클래스에서 반드시 구현해야 할 메서드를 강제하는 추상 클래스를 만들 수 있습니다.
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start_engine(self):
pass
class Car(Vehicle):
def start_engine(self):
print("자동차 시동을 겁니다.")
# vehicle = Vehicle() # 오류! 추상 클래스는 직접 인스턴스화할 수 없음
my_car = Car()
my_car.start_engine() # 출력: 자동차 시동을 겁니다.