14차시: 객체지향 프로그래밍(OOP) 마스터하기

객체지향 프로그래밍(Object-Oriented Programming, OOP)은 데이터를 '객체(object)'로, 처리하는 행위를 '메서드(method)'로 묶어 프로그램을 설계하는 방식입니다. 이를 통해 코드의 재사용성을 높이고 유지보수를 용이하게 할 수 있습니다. 파이썬의 클래스(Class)와 객체를 통해 OOP의 핵심 개념을 마스터해 봅시다.

1. 기초 파이썬 예시 코드 (5개)

클래스를 정의하고 객체를 생성하는 가장 기본적인 방법을 익힙니다.

1) 클래스(Class)와 객체(Object) 생성

# 클래스는 설계도이며, 객체는 그 설계도로 만든 실체(인스턴스)입니다.

# '사람'을 표현하는 Person 클래스 정의
class Person:
    # 클래스의 속성 (Attribute)
    species = "호모 사피엔스"

    # 클래스의 행동 (Method)
    def say_hello(self):
        print("안녕하세요!")

# Person 클래스의 인스턴스(객체) 생성
p1 = Person()

# 객체의 속성과 메서드 사용
print(p1.species)     # 출력: 호모 사피엔스
p1.say_hello()        # 출력: 안녕하세요!

2) 생성자 메서드 __init__

# 객체가 생성될 때 자동으로 호출되어 초기 속성을 설정합니다.

class Student:
    def __init__(self, name, major):
        self.name = name   # 인스턴스 변수 설정
        self.major = major

    def introduce(self):
        print(f"저는 {self.major}을 전공하는 {self.name}입니다.")

# 객체 생성 시 인자 전달
s1 = Student("홍길동", "컴퓨터공학")
s2 = Student("인수샘", "데이터사이언스")

s1.introduce()
s2.introduce()

3) 클래스 변수 vs 인스턴스 변수

# 모든 객체가 공유하는 변수와 각 객체마다 고유한 변수를 구분합니다.

class Dog:
    kind = "강아지" # 클래스 변수 (모든 Dog 객체가 공유)

    def __init__(self, name):
        self.name = name # 인스턴스 변수 (각 Dog마다 다름)

d1 = Dog("바둑이")
d2 = Dog("초코")

print(f"{d1.name}는 {d1.kind}")
print(f"{d2.name}는 {d2.kind}")

4) 메서드 안에서 다른 메서드 호출

class Calculator:
    def add(self, a, b):
        return a + b

    def add_and_print(self, a, b):
        result = self.add(a, b) # self를 통해 자신의 다른 메서드 호출
        print(f"결과값은 {result}입니다.")

calc = Calculator()
calc.add_and_print(10, 20)

5) 정보 은닉 (Encapsulation) 기초

# 변수 앞에 __를 붙여 외부에서 직접 접근하는 것을 제한합니다.

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance # 비공개 속성

    def deposit(self, amount):
        self.__balance += amount
        print(f"{amount}원 입금 완료")

    def get_balance(self):
        return self.__balance

account = BankAccount(1000)
account.deposit(500)
print(f"현재 잔액: {account.get_balance()}원")
# print(account.__balance) # 에러 발생: 외부 접근 불가

2. 응용 파이썬 예시 코드 (5개)

상속, 메서드 오버라이딩 등 객체지향의 강력한 기능을 다룹니다.

1) 상속 (Inheritance)

# 부모 클래스의 기능을 자식 클래스가 그대로 물려받습니다.

class Animal:
    def eat(self):
        print("음식을 먹습니다.")

class Cat(Animal): # Animal을 상속받음
    def meow(self):
        print("야옹!")

my_cat = Cat()
my_cat.eat()  # 부모의 메서드 사용
my_cat.meow() # 자신의 메서드 사용

2) 메서드 오버라이딩 (Method Overriding)

# 부모로부터 받은 메서드를 자식의 필요에 맞게 재정의합니다.

class Bird:
    def fly(self):
        print("하늘을 납니다.")

class Penguin(Bird):
    def fly(self): # 부모의 fly를 덮어씀
        print("펭귄은 날 수 없어 헤엄을 칩니다.")

p = Penguin()
p.fly()

3) super()를 이용한 부모 호출

# 자식의 생성자에서 부모의 생성자나 메서드를 호출할 때 사용합니다.

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

class Employee(Person):
    def __init__(self, name, salary):
        super().__init__(name) # 부모의 __init__ 호출
        self.salary = salary

emp = Employee("구운몽", 5000)
print(f"이름: {emp.name}, 급여: {emp.salary}")

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

# 객체 생성 없이 클래스 이름을 통해 호출하는 메서드들입니다.

class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day

    @staticmethod
    def is_leap_year(year): # 정적 메서드: 객체 상태와 무관한 로직
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

    @classmethod
    def from_string(cls, date_str): # 클래스 메서드: 새로운 객체 생성 로직
        year, month, day = map(int, date_str.split('-'))
        return cls(year, month, day)

print(f"2024년은 윤년인가? {Date.is_leap_year(2024)}")
d = Date.from_string("2024-12-25")
print(f"생성된 날짜: {d.year}년 {d.month}월 {d.day}일")

5) 다형성 (Polymorphism)

# 같은 이름의 메서드가 각 객체의 특성에 맞게 다르게 동작하는 원리입니다.

class Dog:
    def speak(self): return "멍멍!"

class Cat:
    def speak(self): return "야옹~"

class Sheep:
    def speak(self): return "매에~"

# 공통 인터페이스를 사용하는 함수
def make_animal_sound(animal):
    print(animal.speak())

animals = [Dog(), Cat(), Sheep()]
for a in animals:
    make_animal_sound(a)

3. 심화 파이썬 예시 코드 (1개)

1) 추상 기본 클래스 (Abstract Base Class, ABC)

# 자식 클래스가 반드시 특정 메서드를 구현하도록 강제하는 '설계 규약'입니다.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self): # 자식 클래스에서 반드시 구현해야 함
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self): # 추상 메서드 구현
        return 3.14 * (self.radius ** 2)

class Square(Shape):
    def __init__(self, side):
        self.side = side
    
    def area(self): # 추상 메서드 구현
        return self.side * self.side

shapes = [Circle(5), Square(4)]
for s in shapes:
    print(f"도형의 넓이: {s.area()}")