Created at : 2024-09-03 10:48
Auther: Soo.Y
2. 객체 지향 프로그래밍 (OOP) 심화
객체 지향 프로그래밍(OOP)은 소프트웨어 개발에서 매우 중요한 패러다임입니다. 이 장에서는 객체 지향 프로그래밍의 기본 개념을 복습하고, 심화된 주제들을 다루어 더 복잡한 프로그램을 설계하고 구현할 수 있도록 합니다. 주요 주제로는 클래스와 객체, 상속과 다형성, 캡슐화와 추상화, 메타클래스와 클래스 메서드, 정적 메서드, 그리고 연산자 오버로딩이 있습니다.
2.1 클래스와 객체의 개념 복습
객체 지향 프로그래밍의 핵심 개념은 클래스와 객체입니다. 클래스는 객체를 생성하기 위한 청사진(설계도)이며, 객체는 클래스에 정의된 속성과 메서드를 가지는 실체입니다.
2.1.1 클래스와 객체의 기본 개념
-
클래스: 클래스는 관련된 속성과 메서드를 하나로 묶어 정의하는 틀입니다. 예를 들어,
Car
클래스는 자동차의 속성(예: 색상, 모델)과 동작(예: 이동, 정지)을 정의할 수 있습니다. -
객체: 객체는 클래스로부터 생성된 구체적인 인스턴스입니다. 동일한 클래스에서 생성된 객체들은 같은 구조를 가지지만, 각자의 상태를 가질 수 있습니다.
-
예제:
class Car:
def __init__(self, make, model, color):
self.make = make
self.model = model
self.color = color
def start_engine(self):
print(f"{self.make} {self.model}의 엔진이 시동을 걸었습니다.")
# 객체 생성
my_car = Car("Tesla", "Model S", "Red")
my_car.start_engine() # 출력: Tesla Model S의 엔진이 시동을 걸었습니다.
이 예제에서 Car
클래스는 자동차의 정보를 나타내는 속성과 엔진을 시동 거는 메서드를 정의합니다. my_car
는 Car
클래스의 인스턴스입니다.
2.1.2 클래스의 구성 요소
-
속성 (Attributes): 클래스 내에서 정의된 변수로, 객체의 상태를 나타냅니다. 클래스의 생성자 메서드
__init__
을 통해 초기화됩니다. -
메서드 (Methods): 클래스 내에서 정의된 함수로, 객체의 동작을 나타냅니다. 메서드는 첫 번째 인자로 항상
self
를 받으며, 이를 통해 객체 자신의 속성과 다른 메서드에 접근할 수 있습니다. -
예제
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name}이(가) 짖습니다!")
my_dog = Dog("Buddy", 3)
my_dog.bark() # 출력: Buddy이(가) 짖습니다!
2.2 상속, 다형성, 캡슐화, 추상화 심화
객체 지향 프로그래밍의 네 가지 주요 원칙인 상속, 다형성, 캡슐화, 추상화를 심화 학습합니다.
2.2.1 상속 (Inheritance)
상속은 기존 클래스를 기반으로 새로운 클래스를 정의하는 것입니다. 이를 통해 코드의 재사용성을 높이고, 기존 클래스를 확장하여 새로운 기능을 추가할 수 있습니다.
-
기본 개념
- 부모 클래스 (Parent Class): 상속의 원형이 되는 클래스입니다.
- 자식 클래스 (Child Class): 부모 클래스를 상속받아 확장된 클래스입니다.
-
예제
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name}이(가) 소리를 냅니다.")
class Dog(Animal):
def speak(self):
print(f"{self.name}이(가) 멍멍 짖습니다!")
my_dog = Dog("Buddy")
my_dog.speak() # 출력: Buddy이(가) 멍멍 짖습니다!
이 예제에서 Dog
클래스는 Animal
클래스를 상속받아 speak
메서드를 재정의(오버라이딩)했습니다.
2.2.2 다형성 (Polymorphism)
다형성은 동일한 인터페이스를 사용해 다양한 클래스에서 서로 다른 구현을 제공할 수 있는 능력을 의미합니다. 이를 통해 여러 종류의 객체를 동일한 방식으로 처리할 수 있습니다.
-
메서드 오버라이딩: 다형성의 한 예로, 부모 클래스에서 정의한 메서드를 자식 클래스에서 재정의할 수 있습니다. 이때 자식 클래스의 메서드가 호출됩니다.
-
예제:
class Animal:
def speak(self):
raise NotImplementedError("이 메서드는 서브클래스에서 구현되어야 합니다.")
class Dog(Animal):
def speak(self):
return "멍멍!"
class Cat(Animal):
def speak(self):
return "야옹!"
def make_animal_speak(animal):
print(animal.speak())
my_dog = Dog()
my_cat = Cat()
make_animal_speak(my_dog) # 출력: 멍멍!
make_animal_speak(my_cat) # 출력: 야옹!
이 예제에서 make_animal_speak
함수는 Animal
타입의 객체를 받아 speak
메서드를 호출하지만, 실제로는 각 객체의 구체적인 클래스에서 구현된 메서드가 실행됩니다.
2.2.3 캡슐화 (Encapsulation)
캡슐화는 객체의 속성을 외부에서 직접 접근하지 못하도록 숨기고, 공개된 메서드를 통해서만 접근하도록 하는 원칙입니다. 이를 통해 데이터의 무결성을 보호하고, 객체의 내부 구현을 외부에 노출시키지 않을 수 있습니다.
-
접근 제어자
- 공개 속성 (Public Attribute): 클래스 외부에서 접근 가능한 속성입니다.
- 비공개 속성 (Private Attribute): 클래스 외부에서 접근할 수 없는 속성입니다. 이름 앞에 밑줄 두 개(
__
)를 붙여서 정의합니다.
-
예제
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # 비공개 속성
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
else:
print("잔액이 부족합니다.")
def get_balance(self):
return self.__balance
account = BankAccount("홍길동", 1000)
account.deposit(500)
print(account.get_balance()) # 출력: 1500
account.withdraw(2000) # 출력: 잔액이 부족합니다.
이 예제에서 __balance
속성은 비공개로 설정되어 외부에서 직접 접근할 수 없습니다. 대신 get_balance
메서드를 통해 접근할 수 있습니다.
2.2.4 추상화 (Abstraction)
추상화는 객체의 복잡성을 감추고, 중요한 정보만 외부에 노출하는 원칙입니다. 추상 클래스는 직접 인스턴스화할 수 없으며, 서브클래스에서 반드시 구현해야 하는 메서드를 정의합니다.
-
추상 클래스와 메서드
- 추상 클래스는
abc
모듈의ABC
클래스를 상속받아 정의합니다. - 추상 메서드는
@abstractmethod
데코레이터로 정의합니다.
- 추상 클래스는
-
예제
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "멍멍!"
class Cat(Animal):
def speak(self):
return "야옹!"
# Animal 클래스는 추상 클래스이므로 인스턴스화할 수 없습니다.
my_dog = Dog()
print(my_dog.speak()) # 출력: 멍멍!
이 예제에서 Animal
클래스는 추상 클래스이며, speak
메서드는 추상 메서드로 정의되어 서브클래스에서 반드시 구현해야 합니다.
2.3 메타클래스와 클래스 메서드, 정적 메서드
메타클래스와 클래스 메서드, 정적 메서드는 클래스와 객체의 동작을 보다 유연하게 제어할 수 있는 고급 기법입니다.
2.3.1 메타클래스 (Metaclasses)
메타클래스는 클래스의 생성과정을 제어하는 클래스입니다. 파이썬에서 모든 클래스는 type
이라는 메타클래스를 기반으로 생성됩니다. 메타클래스를 사용하면 클래스가 생성되는 방식을 커스터마이징할 수 있습니다.
-
메타클래스의 기본 개념 메타클래스는 클래스의 정의 시점에서 클래스의 속성이나 메서드를 수정하거나 추가할 수 있습니다.
-
예제
class MyMeta(type):
def __new__(cls, name, bases, dct):
dct['category'] = "MyCustomClass"
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=MyMeta):
def __init__(self, value):
self.value = value
obj = MyClass(10)
print(obj.category) # 출력: MyCustomClass
이 예제에서 MyMeta
메타클래스는 클래스가 정의될 때 category
라는 속성을 자동으로 추가합니다.
2.3.2 클래스 메서드 (Class Methods)
클래스 메서드는 클래스 자체를 인자로 받는 메서드입니다. 클래스 메서드는 인스턴스가 아닌 클래스 레벨에서 호출되며, 클래스 변수를 수정하거나 클래스 자체와 관련된 작업을 수행할 때 사용됩니다. 클래스 메서드는 @classmethod
데코레이터를 사용하여 정의합니다.
- 예제
class MyClass:
class_variable = 0
@classmethod
def increment_class_variable(cls):
cls.class_variable += 1
return cls.class_variable
print(MyClass.increment_class_variable()) # 출력: 1
print(MyClass.increment_class_variable()) # 출력: 2
이 예제에서 increment_class_variable
메서드는 클래스 변수 class_variable
을 증가시킵니다.
2.3.3 정적 메서드 (Static Methods)
정적 메서드는 클래스나 인스턴스와 관련이 없는 독립적인 메서드입니다. 정적 메서드는 클래스의 상태나 인스턴스의 상태를 변경하지 않으며, 보통 클래스 내부에 유틸리티 함수로 정의됩니다. 정적 메서드는 @staticmethod
데코레이터를 사용하여 정의합니다.
- 예제:
class MathUtils:
@staticmethod
def add(a, b):
return a + b
print(MathUtils.add(5, 3)) # 출력: 8
이 예제에서 add
메서드는 정적 메서드로, 클래스나 인스턴스의 상태와 무관하게 두 수를 더하는 역할을 합니다.
2.4 연산자 오버로딩 (Operator Overloading)
연산자 오버로딩은 파이썬의 내장 연산자를 사용자 정의 객체에 대해 사용할 수 있도록 하는 기법입니다. 이를 통해 객체 간의 연산을 직관적으로 수행할 수 있습니다. 연산자 오버로딩은 특정 메서드를 구현하여 이루어집니다.
2.4.1 연산자 오버로딩의 기본 개념
연산자 오버로딩은 기본적으로 파이썬의 특별 메서드를 구현하여 이루어집니다. 예를 들어, 덧셈 연산자 +
를 오버로딩하려면 __add__
메서드를 구현합니다.
- 주요 연산자 오버로딩 메서드:
__add__(self, other)
:+
연산자__sub__(self, other)
:-
연산자__mul__(self, other)
:*
연산자__truediv__(self, other)
:/
연산자__eq__(self, other)
:==
연산자__lt__(self, other)
:<
연산자
2.4.2 연산자 오버로딩 예제
- 예제:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(5, 7)
v3 = v1 + v2
print(v3) # 출력: Vector(7, 10)
이 예제에서 Vector
클래스는 __add__
메서드를 구현하여 +
연산자를 오버로딩합니다. 이를 통해 두 벡터 객체를 더하는 연산을 직관적으로 수행할 수 있습니다.
요약
이 장에서는 객체 지향 프로그래밍의 심화 주제들을 다루었습니다. 클래스와 객체의 개념을 복습한 후, 상속과 다형성을 통해 코드의 재사용성과 유연성을 높이는 방법을 배웠습니다. 또한, 캡슐화와 추상화를 통해 객체의 데이터를 보호하고 복잡성을 감추는 기법을 익혔습니다. 마지막으로, 메타클래스, 클래스 메서드, 정적 메서드를 통해 클래스를 보다 유연하게 정의하고 관리하는 방법과, 연산자 오버로딩을 통해 객체 간의 연산을 직관적으로 구현하는 방법을 학습했습니다.