객체 지향 프로그래밍(Object-Oriented Programming)은 현대적인 프로그래밍 패러다임으로, 코드를 객체들의 모임으로 구성하고 객체 간의 상호작용을 중심으로 프로그램을 설계하는 방법론이다. 파이썬은 강력한 객체 지향 프로그래밍 기능을 제공하며, 이를 활용하여 유지보수가 용이하고 재사용성이 높은 프로그램을 개발할 수 있다.
클래스와 객체
클래스는 객체를 생성하기 위한 설계도이며, 객체의 속성(attribute)과 동작(method)을 정의한다.
객체는 클래스의 인스턴스로, 실제로 메모리에 할당된 것을 의미한다.
class People:
def __init__(self, name):
self.name = name
def proverb(self):
print(f"{self.name}가 말했다.")
my_opinion = People("셰익스피어")
my_opinion.proverb() # 출력: 셰익스피어가 말했다.
People 클래스는 name 매개변수를 받아 객체를 초기화하는 생성자(__init__)를 가지고 있다. proverb 메서드는 해당 객체의 name 속성을 이용하여 특정 문구를 출력하며, my_opinion 객체를 생성하여 "셰익스피어가 말했다."라는 문구를 출력한다.
캡슐화(Encapsulation)
캡슐화는 객체의 데이터와 메서드를 하나로 묶는 것을 의미한다.
클래스는 데이터를 캡슐화하여 데이터의 접근을 제어하고, 메서드를 통해 데이터를 조작한다.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
else:
print("잔액이 부족합니다.")
my_account = BankAccount()
my_account.deposit(100)
my_account.withdraw(50)
print(my_account.balance) # 출력: 50
BankAccount 클래스는 balance 속성을 가지며, 초기값은 0으로 설정된다. deposit 메서드는 매개변수로 받은 금액을 balance에 더하며, withdraw 메서드는 매개변수로 받은 금액을 balance에서 뺄 수 있다.
만약 잔액이 충분하지 않으면 "잔액이 부족합니다."라는 메시지를 출력한다. 마지막에 my_account 객체를 생성한 후 100을 예금하고 50을 인출한 후 잔액인 50을 출력한다.
상속(Inheritance)
상속은 기존 클래스의 속성과 동작을 다른 클래스가 물려받는 것을 의미한다.
상속을 통해 코드의 재사용성을 높이고, 클래스 간의 계층 구조를 형성할 수 있다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class People(Animal):
def proverb(self):
print(f"{self.name}가 말했다.")
my_opinion = People("셰익스피어")
my_opinion.proverb() # 출력: 셰익스피어가 말했다.
Animal 클래스는 name 매개변수를 받아 객체를 초기화하는 생성자(__init__)를 가지고 있다. speak 메서드는 상속 받은 클래스에서 재정의될 것을 가정하고 비어 있으며, People 클래스는 Animal 클래스를 상속받아 생성된다.
proverb 메서드는 name 속성을 이용하여 특정 문구를 출력하고, my_opinion 객체를 생성하여 "셰익스피어가 말했다."라는 문구를 출력한다.
다형성(Polymorphism)
다형성은 동일한 메서드를 다른 방식으로 동작하게 하는 개념이다.
부모 클래스의 메서드를 자식 클래스에서 오버라이딩하거나, 동일한 인터페이스를 가진 다른 클래스들이 서로 다른 동작을 수행할 수 있다.
class Shape:
def calculate_area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14 * self.radius * self.radius
def print_area(shape):
area = shape.calculate_area()
print(f"The area of the shape is {area} square units.")
my_rectangle = Rectangle(5, 3)
my_circle = Circle(4)
print_area(my_rectangle) # 출력: The area of the shape is 15 square units.
print_area(my_circle) # 출력: The area of the shape is 50.24 square units.
위의 코드에서 Shape 클래스를 상속받은 Rectangle 클래스와 Circle 클래스를 정의한다. Shape 클래스에는 calculate_area 메서드가 정의되어 있으며, 이 메서드는 도형의 넓이를 계산하여 반환하는 역할을 한다.
각 도형 클래스에서는 calculate_area 메서드를 재정의하여 해당 도형의 넓이를 구하는 방식을 구현하며, Rectangle 클래스는 가로와 세로 길이를 이용하여 넓이를 계산하고, Circle 클래스는 반지름을 이용하여 넓이를 계산한다.
print_area 함수는 매개변수로 받은 도형 객체의 calculate_area 메서드를 호출하여 도형의 넓이를 계산하고 출력한다. 이렇게 함으로써 동일한 함수를 사용하면서도 각 도형의 특징에 따라 다른 동작을 할 수 있다.
추상화(Abstraction)
추상화는 객체의 공통된 속성과 동작을 추출하여 클래스로 정의하는 것을 의미한다.
추상 클래스는 인스턴스화할 수 없으며, 서브 클래스에서 구체화되어야 한다.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
my_rectangle = Rectangle(4, 5)
print(my_rectangle.area()) # 출력: 20
클래스 상속과 다중 상속
클래스 상속은 기존 클래스의 속성과 동작을 다른 클래스에게 물려주는 개념이다.
다중 상속은 하나의 클래스가 여러 개의 부모 클래스로부터 상속받는 것을 의미하며, 이를 통해 다양한 부모 클래스의 속성과 동작을 하나의 클래스에서 모두 사용할 수 있다.
class Shape:
def __init__(self, color):
self.color = color
def area(self):
pass
class Rectangle(Shape):
def __init__(self, color, width, height):
super().__init__(color)
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
my_rectangle = Rectangle("blue", 4, 5)
my_circle = Circle("red", 3)
print(my_rectangle.area()) # 출력: 20
print(my_circle.area()) # 출력: 28.26
특수한 클래스 메서드와 속성
파이썬은 특수한 클래스 메서드와 속성을 제공하여 클래스의 동작을 제어할 수 있다. 예를 들어, __init__ 메서드는 객체의 초기화를 담당하고, __str__ 메서드는 객체의 문자열 표현을 반환한다.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def __str__(self):
return f"{self.brand} {self.model}"
my_car = Car("Tesla", "Model 3")
print(my_car) # 출력: Tesla Model 3
클래스 변수와 인스턴스 변수
클래스 변수는 클래스의 모든 인스턴스가 공유하는 변수이며, 인스턴스 변수는 개별 객체에 속하는 변수이다.
클래스 변수는 클래스에 속하고, 인스턴스 변수는 객체에 속한다.
class Dog:
breed = "Labrador" # 클래스 변수
def __init__(self, name):
self.name = name # 인스턴스 변수
dog1 = Dog("Max")
dog2 = Dog("Charlie")
print(dog1.name) # 출력: Max
print(dog2.name) # 출력: Charlie
print(dog1.breed) # 출력: Labrador
print(dog2.breed) # 출력: Labrador
객체 지향 설계 원칙
SOLID 원칙은 객체 지향 설계의 원칙을 정의하는 다섯 가지 원칙을 의미한다.
각 원칙은 단일 책임 원칙, 개방 폐쇄 원칙, 리스코프 치환 원칙, 인터페이스 분리 원칙, 의존성 역전 원칙을 의미하며, 이러한 원칙을 따르면 유지보수 가능하고 확장 가능한 코드를 작성할 수 있다.
은행 계좌 시스템 예제: Account 클래스와 SavingsAccount, CheckingAccount 클래스를 만들고, 상속과 다형성을 활용하여 계좌의 특징을 모델링한다.
class Account:
def __init__(self, account_number, balance):
self.account_number = account_number
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
else:
print("잔액이 부족합니다.")
class SavingsAccount(Account):
def __init__(self, account_number, balance, interest_rate):
super().__init__(account_number, balance)
self.interest_rate = interest_rate
def apply_interest(self):
interest = self.balance * self.interest_rate
self.deposit(interest)
class CheckingAccount(Account):
def __init__(self, account_number, balance, overdraft_limit):
super().__init__(account_number, balance)
self.overdraft_limit = overdraft_limit
def withdraw(self, amount):
if self.balance + self.overdraft_limit >= amount:
self.balance -= amount
else:
print("인출이 불가능합니다.")
savings_account = SavingsAccount("123456789", 10000, 0.05)
checking_account = CheckingAccount("987654321", 5000, 1000)
savings_account.deposit(500)
savings_account.apply_interest()
print(savings_account.balance) # 출력: 10550.0
checking_account.withdraw(2000)
checking_account.withdraw(6000) # 출력: 인출이 불가능합니다.
이 예제에서는 Account 클래스는 모든 계좌의 공통된 속성과 동작을 정의한다. SavingsAccount 클래스와 CheckingAccount 클래스는 Account 클래스를 상속받아 각각의 특징을 추가로 구현한다.