프로그래밍 패러다임: 절차지향과 객체지향
프로그래밍에는 두 가지 주요 접근 방식이 존재한다. 하나는 절차지향적 사고방식이고, 다른 하나는 객체지향적 사고방식이다.
절차지향 프로그래밍 문제 해결을 위한 단계를 나누어 각 단계별로 코드를 작성하는 방식이다. 이 방법은 대부분의 프로그램에서 기본적으로 사용되며, 규모에 따라 비중이 달라질 뿐이다.
객체지향 프로그래밍 데이터와 기능을 하나의 단위로 묶어 '객체'로 표현하고, 이 객체들이 어떻게 상호작용하는지를 중심으로 설계한다. 예를 들어 게임 개발 시 플레이어 캐릭터나 환경 자원은 모두 독립적인 객체로 모델링된다. 사용자의 입력에 따라 객체의 상태가 변화하며, 개발자는 이러한 객체들의 행동 규칙을 정의하는 데 집중한다.
객체지향과 절차지향은 서로 보완적이며, 실제 프로젝트에서는 혼합하여 사용된다. 문제의 성격에 따라 어느 방식이 더 적합한지는 다르다.
클래스와 인스턴스의 관계
클래스는 동일한 속성과 행동을 공유하는 객체들의 틀이다. 인스턴스는 클래스로부터 생성된 구체적인 객체이며, 클래스는 인스턴스의 설계도 역할을 한다.
class Vehicle:
brand = "Unknown"
def __init__(self, model):
self.model = model
def start_engine(self):
return f"{self.model} 엔진 시작"
인스턴스 생성 및 속성 접근:
car1 = Vehicle("Tesla")
car2 = Vehicle("Hyundai")
print(car1.brand) # Unknown
print(car1.model) # Tesla
# 인스턴스만의 고유 속성 추가
car1.year = 2023
print(car1.__dict__) # {'model': 'Tesla', 'year': 2023}
print(car2.__dict__) # {'model': 'Hyundai'}
속성 변경과 동작 원리
인스턴스의 속성은 . 연산자로 직접 수정 가능하며, 이는 클래스 수준의 속성에는 영향을 주지 않는다.
car1.brand = "NewBrand"
print(car1.brand) # NewBrand
print(Vehicle.brand) # Unknown (클래스 속성은 그대로)
또한 인스턴스에 새로운 속성을 동적으로 추가할 수 있다.
car1.color = "Red"
초기화 메서드: __init__
인스턴스 생성 시 자동으로 호출되는 특수 메서드로, 인스턴스 고유의 초기값을 설정한다.
class Animal:
species = "Unknown"
def __init__(self, name, age):
self.name = name
self.age = age
dog = Animal("Buddy", 5)
print(dog.name) # Buddy
print(dog.age) # 5
메서드의 종류: 바인딩 방식
인스턴스 메서드 (self)
클래스 내부의 메서드는 기본적으로 인스턴스에 바인딩되어 있으며, 첫 번째 인자로 self가 전달된다.
class Person:
def __init__(self, name):
self.name = name
def greet(self):
return f"안녕, 나는 {self.name}이야."
p1 = Person("Alice")
print(p1.greet()) # 안녕, 나는 Alice이야.
클래스 메서드 (@classmethod)
클래스 자체에 바인딩되는 메서드로, cls 매개변수를 통해 클래스 정보에 접근할 수 있다.
class Config:
env = "production"
@classmethod
def get_env(cls):
return cls.env
print(Config.get_env()) # production
정적 메서드 (@staticmethod)
바인딩되지 않은 일반 함수와 유사하며, 클래스나 인스턴스를 통하지 않고도 호출 가능하다.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
result = MathUtils.add(3, 4)
print(result) # 7
상속과 이름 검색 순서
상속은 공통적인 속성과 기능을 부모 클래스에서 추출하여 자식 클래스가 재사용하는 방식이다.
class Parent:
value = 100
class Child(Parent):
pass
child = Child()
print(child.value) # 100 (부모 클래스의 속성 접근)
다중 상속과 MRO
파이썬은 다중 상속을 지원하며, 이름 검색 순서는 mro() 메서드로 확인할 수 있다.
class A:
x = "A"
class B(A):
x = "B"
class C(A):
x = "C"
class D(B, C):
pass
print(D.mro())
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
- 비마름모형: 왼쪽부터 깊이 우선 탐색
- 마름모형: 교차점은 마지막에 처리되며, 지역적으로 넓이 우선 탐색처럼 작동
상속 확장: super() 활용
자식 클래스에서 부모 클래스의 메서드를 오버라이드하면서도 원래 기능을 유지하고 싶을 때 super()를 사용한다.
class Base:
def method(self):
return "Base called"
class Derived(Base):
def method(self):
result = super().method() # 부모의 메서드 호출
return f"{result} + Extended"
d = Derived()
print(d.method()) # Base called + Extended
이렇게 하면 기존 로직을 유지하면서도 확장이 가능해진다.