Python 객체 지향 프로그래밍
OOP는 상태를 소유하고 명확한 인터페이스로 동작을 제공하는 객체 중심으로 프로그램을 구성합니다.
Python 객체 지향 프로그래밍이란?
OOP는 협력하는 객체로 시스템을 모델링합니다. 각 객체가 상태와 허용된 연산을 결합하여 큰 프로그램에서도 책임과 변경 경계를 명확하게 합니다.
OOP의 네 가지 핵심 원칙
- 캡슐화: 상태와 동작을 함께 두고 접근을 제어합니다.
- 추상화: 필요한 인터페이스를 제공하고 불필요한 구현을 숨깁니다.
- 상속: 진정한 is-a 관계일 때 기존 클래스를 특수화합니다.
- 다형성: 서로 다른 객체에 같은 연산을 사용하고 각자의 동작을 실행합니다.
예제 설계
- BankAccount가 잔액을 소유하고 입금을 검증합니다.
- SavingsAccount가 기본 계약을 재사용합니다.
- month_end를 오버라이드해 이자를 적용합니다.
- 반복문은 동일한 인터페이스로 모든 계좌를 처리합니다.
설계 팁: 객체가 다른 객체를 단순히 사용한다면 합성을 우선하고, 자식이 부모를 올바르게 대체할 때만 상속을 사용하세요.
기본 예제
main.py
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = float(balance)
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def month_end(self):
return "No interest"
class SavingsAccount(BankAccount):
def __init__(self, owner, balance, interest_rate):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def month_end(self):
self._balance *= 1 + self.interest_rate
return "Interest applied"
accounts = [
BankAccount("Ada", 100),
SavingsAccount("Lin", 200, 0.05),
]
accounts[0].deposit(50)
for account in accounts:
status = account.month_end()
print(f"{account.owner}: {account.balance:.2f} ({status})")
예상 출력
Ada: 150.00 (No interest)
Lin: 210.00 (Interest applied)
OOP의 네 가지 원칙 실습
이 예제들은 Python에서 캡슐화, 추상화, 상속, 다형성을 일상적인 코드에 적용하는 방법을 보여 줍니다.
캡슐화
상태와 동작을 함께 두고 접근을 제어합니다.
encapsulation.py
class BankAccount:
def __init__(self, balance=0):
# Double underscores prevent accidental direct access.
self.__balance = balance
@property
def balance(self):
# Expose read access without exposing mutation.
return self.__balance
def deposit(self, amount):
# Keep validation next to the state it protects.
if amount <= 0:
raise ValueError("Amount must be positive")
self.__balance += amount
account = BankAccount(100)
account.deposit(50)
print(account.balance) # 150
추상화
필요한 인터페이스를 제공하고 불필요한 구현을 숨깁니다.
abstraction.py
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
# Callers know what pay does, not how each provider does it.
pass
class CreditCard(PaymentMethod):
def pay(self, amount):
# The concrete class hides its payment implementation.
return f"Paid ${amount:.2f} by card"
payment = CreditCard()
print(payment.pay(25))
상속
진정한 is-a 관계일 때 기존 클래스를 특수화합니다.
inheritance.py
class Employee:
def __init__(self, name):
self.name = name
def describe(self):
return self.name
class Developer(Employee):
def __init__(self, name, language):
# Reuse initialization from the parent class.
super().__init__(name)
self.language = language
def describe(self):
# Extend inherited behavior instead of duplicating it.
return f"{super().describe()} writes {self.language}"
print(Developer("Ada", "Python").describe())
다형성
서로 다른 객체에 같은 연산을 사용하고 각자의 동작을 실행합니다.
polymorphism.py
class EmailNotification:
def send(self, message):
return f"Email: {message}"
class SmsNotification:
def send(self, message):
return f"SMS: {message}"
def notify(channels, message):
for channel in channels:
# The same call selects behavior from each concrete object.
print(channel.send(message))
channels = [EmailNotification(), SmsNotification()]
notify(channels, "Order shipped")
작동 원리
BankAccount는 잔액을 캡슐화합니다. SavingsAccount는 인터페이스를 상속하고 month_end를 오버라이드합니다. 반복문은 두 객체를 동일하게 다루며 런타임에 알맞은 메서드가 선택됩니다.
값을 변경하고 Python 설치 없이 CodeUtility 온라인 Python 컴파일러에서 실행하세요.
연습 문제
입력값과 경계 조건을 바꾸고 더 큰 데이터에서도 동작을 확인하세요.
- 실행 전에 출력을 예측하세요.
- 빈 입력과 잘못된 입력을 처리하세요.
- 로직을 함수로 만들고 테스트를 추가하세요.