// education

객체지향 프로그래밍

들어가며

객체지향 프로그래밍(OOP)은 대규모 프로젝트를 체계적으로 구조화하는 핵심 패러다임입니다. 클래스, 객체, 상속, 다형성 등의 개념을 통해 더 모듈화되고 유지보수가 쉬운 코드를 작성할 수 있습니다.

클래스와 객체

예제 1: 기본 클래스 정의

class Dog:
    """개를 표현하는 클래스"""
    
    def __init__(self, name, age):
        """생성자"""
        self.name = name
        self.age = age
    
    def bark(self):
        """짖기"""
        return f"{self.name}가 멍멍합니다"
    
    def age_in_human_years(self):
        """개 나이를 인간 나이로 변환"""
        return self.age * 7

# 객체 생성
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

print(f"강아지1: {dog1.name}, {dog1.age}세")
print(dog1.bark())
print(f"개의 나이(인간): {dog1.age_in_human_years()}세")

print(f"\n강아지2: {dog2.name}, {dog2.age}세")
print(dog2.bark())

출력:

강아지1: Buddy, 3세
Buddy가 멍멍합니다
개의 나이(인간): 21세

강아지2: Max, 5세
Max가 멍멍합니다

예제 2: 속성과 메서드

class Person:
    """사람을 표현하는 클래스"""
    
    # 클래스 변수
    species = "Homo sapiens"
    
    def __init__(self, name, age):
        # 인스턴스 변수
        self.name = name
        self.age = age
    
    def introduce(self):
        """자기소개"""
        return f"안녕하세요, 저는 {self.name}이고 {self.age}살입니다"
    
    def have_birthday(self):
        """생일을 맞이"""
        self.age += 1
        return f"{self.name}이 {self.age}살이 되었습니다"

# 사용
person = Person("Alice", 25)
print(person.introduce())
print(f"종: {person.species}")
print(person.have_birthday())
print(person.introduce())

출력:

안녕하세요, 저는 Alice이고 25살입니다
종: Homo sapiens
Alice이 26살이 되었습니다
안녕하세요, 저는 Alice이고 26살입니다

상속

예제 3: 기본 상속

# 부모 클래스
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return f"{self.name}이 소리를 냅니다"
    
    def move(self):
        return f"{self.name}이 움직입니다"

# 자식 클래스
class Cat(Animal):
    def speak(self):
        """메서드 오버라이딩"""
        return f"{self.name}이 야옹합니다"

class Dog(Animal):
    def speak(self):
        return f"{self.name}이 멍멍합니다"

# 사용
animals = [
    Cat("Whiskers"),
    Dog("Buddy"),
    Animal("Unknown")
]

for animal in animals:
    print(animal.speak())
    print(animal.move())
    print()

출력:

Whiskers이 야옹합니다
Whiskers이 움직입니다

Buddy이 멍멍합니다
Buddy이 움직입니다

Unknown이 소리를 냅니다
Unknown이 움직입니다

예제 4: super() 사용

class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    def get_info(self):
        return f"{self.brand} {self.model}"

class Car(Vehicle):
    def __init__(self, brand, model, doors):
        super().__init__(brand, model)  # 부모 클래스 호출
        self.doors = doors
    
    def get_info(self):
        parent_info = super().get_info()
        return f"{parent_info}, {self.doors}도어"

# 사용
car = Car("Tesla", "Model 3", 4)
print(car.get_info())

출력:

Tesla Model 3, 4도어

특수 메서드

예제 5: 매직 메서드

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    
    def __str__(self):
        """print()에서 사용"""
        return f"{self.title} by {self.author}"
    
    def __repr__(self):
        """인터프리터에서 사용"""
        return f"Book('{self.title}', '{self.author}', {self.pages})"
    
    def __len__(self):
        """len()에서 사용"""
        return self.pages
    
    def __eq__(self, other):
        """== 비교"""
        if isinstance(other, Book):
            return self.title == other.title
        return False
    
    def __lt__(self, other):
        """< 비교"""
        if isinstance(other, Book):
            return self.pages < other.pages
        return False

book1 = Book("1984", "George Orwell", 328)
book2 = Book("Brave New World", "Aldous Huxley", 311)

print(f"str: {book1}")
print(f"repr: {repr(book1)}")
print(f"len: {len(book1)} pages")
print(f"book1 == book2: {book1 == book2}")
print(f"book2 < book1: {book2 < book1}")

출력:

str: 1984 by George Orwell
repr: Book('1984', 'George Orwell', 328)
len: 328 pages
book1 == book2: False
book2 < book1: True

정적 메서드와 클래스 메서드

예제 6: @staticmethod와 @classmethod

class Math:
    """수학 관련 유틸리티"""
    
    PI = 3.14159
    
    @staticmethod
    def add(a, b):
        """정적 메서드 - 인스턴스 필요 없음"""
        return a + b
    
    @classmethod
    def from_diameter(cls, diameter):
        """클래스 메서드 - 클래스 정보 사용"""
        return cls.PI * (diameter / 2) ** 2

# 사용
print(f"정적 메서드: 3 + 5 = {Math.add(3, 5)}")
print(f"클래스 메서드: 반지름 5인 원의 넓이 = {Math.from_diameter(10):.2f}")

# 인스턴스로도 호출 가능
m = Math()
print(f"인스턴스로 호출: {m.add(10, 20)}")

출력:

정적 메서드: 3 + 5 = 8
클래스 메서드: 반지름 5인 원의 넓이 = 78.54
인스턴스로 호출: 30

흔한 실수와 해결책

실수 1: self 매개변수 누락

틀린 예제:

class Counter:
    def __init__(self):
        self.count = 0
    
    def increment():  # self 누락!
        self.count += 1

올바른 예제:

class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        return self.count

실수 2: 클래스 변수와 인스턴스 변수 혼동

틀린 예제:

class Config:
    settings = {}
    
    def __init__(self, key, value):
        self.settings[key] = value

c1 = Config("debug", True)
c2 = Config("timeout", 30)
print(Config.settings)  # {'debug': True, 'timeout': 30}
# 모든 인스턴스가 같은 딕셔너리 공유!

올바른 예제:

class Config:
    def __init__(self, key, value):
        if not hasattr(self, 'settings'):
            self.settings = {}
        self.settings[key] = value

c1 = Config("debug", True)
c2 = Config("timeout", 30)
print(c1.settings)  # {'debug': True}
print(c2.settings)  # {'timeout': 30}

실수 3: 상속 시 super() 호출 누락

틀린 예제:

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        self.breed = breed
        # super().__init__(name) 누락!

dog = Dog("Buddy", "Golden")
print(dog.name)  # AttributeError!

올바른 예제:

class Animal:
    def __init__(self, name):
        self.name = name

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

dog = Dog("Buddy", "Golden")
print(f"{dog.name} ({dog.breed})")

연습 문제

문제 1: 은행 계좌 클래스

입금, 출금, 잔액 조회 기능을 가진 BankAccount 클래스를 작성하세요.

문제 2: 상품 클래스

상품의 이름, 가격, 재고를 관리하고 할인을 적용하는 Product 클래스를 작성하세요.

문제 3: 도형 상속

부모 클래스 Shape과 자식 클래스 Circle, Rectangle을 작성하세요.

풀이

문제 1 풀이

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
    
    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            return f"{amount}원을 입금했습니다"
        return "잘못된 금액입니다"
    
    def withdraw(self, amount):
        if amount > self.balance:
            return "잔액 부족"
        self.balance -= amount
        return f"{amount}원을 출금했습니다"
    
    def get_balance(self):
        return f"현재 잔액: {self.balance}원"

account = BankAccount("Alice", 10000)
print(account.deposit(5000))
print(account.withdraw(3000))
print(account.get_balance())

문제 2 풀이

class Product:
    def __init__(self, name, price, stock):
        self.name = name
        self.price = price
        self.stock = stock
    
    def apply_discount(self, discount_rate):
        return self.price * (1 - discount_rate)
    
    def get_info(self):
        return f"{self.name}: {self.price}원 (재고: {self.stock}개)"

product = Product("노트북", 100000, 5)
print(product.get_info())
print(f"10% 할인: {product.apply_discount(0.1)}원")

문제 3 풀이

import math

class Shape:
    def area(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return math.pi * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

circle = Circle(5)
rect = Rectangle(10, 20)

print(f"원의 넓이: {circle.area():.2f}")
print(f"직사각형의 넓이: {rect.area()}")

마무리

객체지향 프로그래밍을 마스터하면 복잡한 프로젝트도 체계적으로 구조화할 수 있습니다. 각 개념을 충분히 이해하고 실제 프로젝트에 적용해보세요.

← 이전함수와 모듈 설계 다음 →고급 OOP 개념