// education

고급 OOP 개념

들어가며

고급 OOP 개념들을 통해 더 유연하고 확장성 있는 클래스 설계를 할 수 있습니다. 추상 클래스, 다중 상속, 프로퍼티 등의 개념을 학습합니다.

추상 클래스

예제 1: ABC를 사용한 추상 클래스

from abc import ABC, abstractmethod

class Shape(ABC):
    """도형의 추상 클래스"""
    
    @abstractmethod
    def area(self):
        """넓이를 계산합니다"""
        pass
    
    @abstractmethod
    def perimeter(self):
        """둘레를 계산합니다"""
        pass
    
    def description(self):
        """구체적인 메서드"""
        return f"이것은 {self.__class__.__name__}입니다"

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

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

# 사용
circle = Circle(5)
rect = Rectangle(10, 20)

for shape in [circle, rect]:
    print(f"{shape.description()}")
    print(f"넓이: {shape.area():.2f}, 둘레: {shape.perimeter():.2f}\n")

출력:

이것은 Circle입니다
넓이: 78.54, 둘레: 31.42

이것은 Rectangle입니다
넓이: 200.00, 둘레: 60.00

프로퍼티 (Property)

예제 2: @property 데코레이터

class Temperature:
    """온도를 관리하는 클래스"""
    
    def __init__(self, celsius):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """섭씨 온도 (읽기)"""
        return self._celsius
    
    @property
    def fahrenheit(self):
        """화씨 온도 (계산)"""
        return self._celsius * 9/5 + 32
    
    @celsius.setter
    def celsius(self, value):
        """섭씨 온도 (쓰기)"""
        if value < -273.15:
            raise ValueError("절대영도보다 낮을 수 없습니다")
        self._celsius = value

temp = Temperature(25)
print(f"섭씨: {temp.celsius}°C")
print(f"화씨: {temp.fahrenheit}°F")

temp.celsius = 30
print(f"\n변경 후:")
print(f"섭씨: {temp.celsius}°C")
print(f"화씨: {temp.fahrenheit}°F")

# 잘못된 값 설정 시도
# temp.celsius = -300  # ValueError!

출력:

섭씨: 25°C
화씨: 77.0°F

변경 후:
섭씨: 30°C
화씨: 86.0°F

다중 상속

예제 3: 다중 상속과 MRO

class Swimmer:
    def swim(self):
        return "수영을 합니다"

class Flyer:
    def fly(self):
        return "날아갑니다"

class Duck(Swimmer, Flyer):
    """오리: 헤엄도 치고 날아갑니다"""
    pass

class Penguin(Swimmer):
    """펭귄: 헤엄만 칩니다"""
    pass

# 사용
duck = Duck()
print(f"오리: {duck.swim()}, {duck.fly()}")

penguin = Penguin()
print(f"펭귄: {penguin.swim()}")

# MRO 확인
print(f"\nDuck의 MRO: {Duck.__mro__}")

출력:

오리: 수영을 합니다, 날아갑니다
펭귄: 수영을 합니다

Duck의 MRO: (<class 'Duck'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)

매직 메서드 심화

예제 4: 컨테이너 매직 메서드

class Inventory:
    """재고 관리 클래스"""
    
    def __init__(self, items=None):
        self.items = items if items else {}
    
    def __len__(self):
        """재고 종류 수"""
        return len(self.items)
    
    def __getitem__(self, key):
        """items['product'] 형식으로 접근"""
        return self.items.get(key, 0)
    
    def __setitem__(self, key, value):
        """items['product'] = 10 형식으로 설정"""
        self.items[key] = value
    
    def __contains__(self, key):
        """'product' in items 형식으로 확인"""
        return key in self.items
    
    def __iter__(self):
        """for item in inventory 형식으로 순회"""
        return iter(self.items)

# 사용
inv = Inventory()
inv["apple"] = 10
inv["banana"] = 5
inv["orange"] = 8

print(f"재고 종류: {len(inv)}")
print(f"사과 재고: {inv['apple']}")
print(f"'apple' in inv: {'apple' in inv}")
print(f"모든 상품: {list(inv)}")

출력:

재고 종류: 3
사과 재고: 10
'apple' in inv: True
모든 상품: ['apple', 'banana', 'orange']

클래스 메서드와 데코레이터

예제 5: 싱글톤 패턴

class Database:
    """싱글톤 패턴 - 인스턴스가 하나만 존재"""
    
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        self.connection = "Database Connection"
    
    def query(self, sql):
        return f"실행: {sql}"

# 사용
db1 = Database()
db2 = Database()

print(f"db1 == db2: {db1 is db2}")
print(f"db1의 connection: {db1.connection}")
print(f"db2의 connection: {db2.connection}")

출력:

db1 == db2: True
db1의 connection: Database Connection
db2의 connection: Database Connection

메타클래스 기초

예제 6: 간단한 메타클래스

class SingletonMeta(type):
    """싱글톤 메타클래스"""
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(metaclass=SingletonMeta):
    def __init__(self):
        self.logs = []
    
    def log(self, message):
        self.logs.append(message)
        return f"로그: {message}"

# 사용
logger1 = Logger()
logger1.log("첫 번째 메시지")

logger2 = Logger()
logger2.log("두 번째 메시지")

print(f"logger1 == logger2: {logger1 is logger2}")
print(f"모든 로그: {logger1.logs}")

출력:

logger1 == logger2: True
모든 로그: ['첫 번째 메시지', '두 번째 메시지']

흔한 실수와 해결책

실수 1: 다중 상속에서 충돌

틀린 예제:

class A:
    def method(self):
        return "A"

class B:
    def method(self):
        return "B"

class C(A, B):
    pass

c = C()
print(c.method())  # "A" - 의도와 다를 수 있음

올바른 예제:

class A:
    def method(self):
        return "A"

class B:
    def method(self):
        return "B"

class C(A, B):
    def method(self):
        # 명시적으로 어느 부모를 사용할지 지정
        return f"{super().method()}, C"

c = C()
print(c.method())  # "A, C"

실수 2: 프로퍼티와 일반 속성 혼동

틀린 예제:

class Person:
    @property
    def age(self):
        return self._age
    
    def __init__(self, age):
        self.age = age  # age 프로퍼티에 직접 쓰려고 함

p = Person(25)  # AttributeError!

올바른 예제:

class Person:
    def __init__(self, age):
        self._age = age  # 내부 속성에 저장
    
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        self._age = value

p = Person(25)
print(p.age)  # 25
p.age = 26
print(p.age)  # 26

실수 3: 추상 메서드 구현하지 않음

틀린 예제:

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def method(self):
        pass

# class Child(Base):  # 에러: 추상 메서드 미구현
#     pass

올바른 예제:

from abc import ABC, abstractmethod

class Base(ABC):
    @abstractmethod
    def method(self):
        pass

class Child(Base):
    def method(self):
        return "구현됨"

c = Child()
print(c.method())

연습 문제

문제 1: 학생 성적 관리

Student 클래스를 만들어 성적을 프로퍼티로 관리하세요.

문제 2: 플러그인 시스템

추상 클래스를 사용하여 확장 가능한 플러그인 시스템을 설계하세요.

문제 3: 설정 관리자

싱글톤 패턴을 사용한 설정 관리 클래스를 만드세요.

풀이

문제 1 풀이

class Student:
    def __init__(self, name):
        self.name = name
        self._scores = []
    
    @property
    def scores(self):
        return self._scores
    
    @property
    def average(self):
        if not self._scores:
            return 0
        return sum(self._scores) / len(self._scores)
    
    def add_score(self, score):
        if 0 <= score <= 100:
            self._scores.append(score)
        else:
            raise ValueError("점수는 0~100 사이여야 합니다")

student = Student("Alice")
student.add_score(85)
student.add_score(90)
student.add_score(78)

print(f"학생: {student.name}")
print(f"평균 점수: {student.average:.1f}")

문제 2 풀이

from abc import ABC, abstractmethod

class Plugin(ABC):
    @abstractmethod
    def execute(self):
        pass

class HelloPlugin(Plugin):
    def execute(self):
        return "Hello Plugin"

class WorldPlugin(Plugin):
    def execute(self):
        return "World Plugin"

class PluginManager:
    def __init__(self):
        self.plugins = []
    
    def register(self, plugin):
        self.plugins.append(plugin)
    
    def run_all(self):
        for plugin in self.plugins:
            print(plugin.execute())

manager = PluginManager()
manager.register(HelloPlugin())
manager.register(WorldPlugin())
manager.run_all()

문제 3 풀이

class Config(type):
    _instance = None
    
    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance

class Settings(metaclass=Config):
    def __init__(self):
        self.settings = {}
    
    def set(self, key, value):
        self.settings[key] = value
    
    def get(self, key):
        return self.settings.get(key)

s1 = Settings()
s1.set("debug", True)

s2 = Settings()
print(f"debug: {s2.get('debug')}")
print(f"s1 is s2: {s1 is s2}")

마무리

고급 OOP 개념을 이해하면 더 우아하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.

← 이전객체지향 프로그래밍 다음 →예외 처리와 로깅