Created at : 2024-09-02 14:19
Auther: Soo.Y

8. 객체 지향 프로그래밍 (Object-Oriented Programming)

객체 지향 프로그래밍(OOP)은 프로그램을 객체의 집합으로 구성하여 데이터와 그 데이터를 처리하는 메서드를 하나의 단위로 묶어 표현하는 방식입니다. 파이썬은 객체 지향 프로그래밍을 지원하며, 이를 통해 코드의 재사용성과 유지보수성을 높일 수 있습니다. 이 장에서는 객체 지향 프로그래밍의 기본 개념과 파이썬에서 클래스를 정의하고 사용하는 방법을 다룹니다.

8.1 객체 지향 프로그래밍의 기본 개념

객체 지향 프로그래밍에서는 프로그램을 구성하는 기본 단위로 객체를 사용합니다. 객체는 데이터(속성)와 이 데이터를 처리하는 함수(메서드)를 포함합니다. 객체 지향 프로그래밍의 주요 개념은 다음과 같습니다:

  • 클래스(Class): 객체를 정의하는 설계도입니다. 클래스는 객체의 속성과 메서드를 정의합니다.
  • 객체(Object): 클래스에 의해 생성된 실제 인스턴스(instance)입니다.
  • 속성(Attribute): 객체의 상태를 나타내는 변수입니다.
  • 메서드(Method): 객체가 수행할 수 있는 동작(함수)입니다.
  • 상속(Inheritance): 기존 클래스(부모 클래스)를 기반으로 새로운 클래스(자식 클래스)를 생성하는 기능입니다.
  • 다형성(Polymorphism): 같은 이름의 메서드가 다른 클래스에서 다르게 동작할 수 있도록 하는 기능입니다.
  • 캡슐화(Encapsulation): 객체의 속성과 메서드를 하나로 묶고, 외부로부터 내부를 숨기는 개념입니다.

8.2 클래스와 객체

파이썬에서 클래스를 정의하고 객체를 생성하는 방법을 알아봅시다.

  • 클래스 정의:
class 클래스이름:
    def __init__(self, 매개변수들):
        self.속성 = 초기값
 
    def 메서드이름(self, 매개변수들):
        메서드 내용
  • 예제:
class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed
 
    def bark(self):
        return f"{self.name}가 짖습니다!"
 
# 객체 생성
my_dog = Dog("바둑이", "진돗개")
 
# 메서드 호출
print(my_dog.bark())  # 출력: 바둑이가 짖습니다!

위 예제에서 Dog 클래스는 namebreed라는 두 개의 속성을 가지며, bark라는 메서드를 가지고 있습니다. __init__ 메서드는 생성자(constructor)로, 객체가 생성될 때 호출되어 객체의 초기 상태를 설정합니다.

8.3 클래스 속성과 인스턴스 속성

  • 인스턴스 속성: 인스턴스 속성은 각 객체에 개별적으로 존재하는 속성입니다. 인스턴스 속성은 객체마다 고유하며, 객체가 생성될 때 __init__ 메서드에서 정의됩니다. 각 객체는 독립적으로 인스턴스 속성을 가지며, 다른 객체와 값을 공유하지 않습니다.
class Car:
    def __init__(self, model, color):
        self.model = model
        self.color = color
 
car1 = Car("Tesla Model 3", "red")
car2 = Car("BMW i8", "blue")
 
print(car1.model)  # 출력: Tesla Model 3
print(car2.model)  # 출력: BMW i8
  • 클래스 속성: 클래스 속성은 클래스 자체에 속하는 속성으로, 모든 인스턴스가 공유합니다. 클래스 속성은 클래스 정의 내부에 위치하며, 모든 객체가 동일한 값을 공유합니다. 클래스 속성은 클래스 이름을 통해 접근할 수 있습니다.
class Car:
    wheels = 4  # 클래스 속성
 
    def __init__(self, model, color):
        self.model = model
        self.color = color
 
car1 = Car("Tesla Model 3", "red")
car2 = Car("BMW i8", "blue")
 
print(car1.wheels)  # 출력: 4
print(car2.wheels)  # 출력: 4
 
# 클래스 속성은 모든 인스턴스에서 공유됩니다.
Car.wheels = 3
print(car1.wheels) # 출력: 3
print(car2.wheels) # 출력: 3

8.4 메서드 종류

파이썬에서 메서드는 인스턴스 메서드, 클래스 메서드, 정적 메서드로 나뉩니다.

  • 인스턴스 메서드: 인스턴스 메서드는 특정 객체(인스턴스)에 종속된 메서드입니다. 인스턴스 메서드는 객체의 상태(속성)를 변경하거나, 인스턴스와 관련된 작업을 수행할 때 사용됩니다. 인스턴스 메서드는 항상 첫 번째 매개변수로 self를 받으며, 이를 통해 인스턴스 속성과 메서드에 접근할 수 있습니다.
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
    def area(self):
        return 3.14 * (self.radius ** 2)
 
c = Circle(5)
print(c.area())  # 출력: 78.5
  • 클래스 메서드: 클래스 메서드는 클래스 자체에 종속된 메서드입니다. 클래스 메서드는 특정 인스턴스가 아닌 클래스 전체와 관련된 작업을 수행할 때 사용됩니다. 클래스 메서드는 @classmethod 데코레이터로 정의되며, 첫 번째 매개변수로 cls를 받습니다. cls는 현재 클래스를 가리키며, 이를 통해 클래스 속성에 접근하거나 클래스를 변경할 수 있습니다.
class Circle:
    pi = 3.14
 
    @classmethod
    def set_pi(cls, new_pi):
        cls.pi = new_pi
 
Circle.set_pi(3.14159)
print(Circle.pi)  # 출력: 3.14159
  • 정적 메서드: 정적 메서드는 클래스나 인스턴스에 종속되지 않은 메서드입니다. 정적 메서드는 객체 상태나 클래스 상태를 변경하지 않고, 주로 유틸리티 함수를 정의할 때 사용됩니다. 정적 메서드는 @staticmethod 데코레이터로 정의되며, selfcls 매개변수를 사용하지 않습니다.
class Math:
    @staticmethod
    def add(a, b):
        return a + b
 
print(Math.add(3, 5))  # 출력: 8

8.5 상속 (Inheritance)

상속은 기존 클래스를 기반으로 새로운 클래스를 생성하는 기능입니다. 상속을 통해 코드 재사용이 가능하며, 부모 클래스의 모든 속성과 메서드를 자식 클래스가 물려받을 수 있습니다.

  • 기본 상속:
class Animal:
    def __init__(self, name):
        self.name = name
 
    def speak(self):
        return f"{self.name}가 소리를 냅니다."
 
class Dog(Animal):
    def speak(self):
        return f"{self.name}가 짖습니다!"
 
my_dog = Dog("바둑이")
print(my_dog.speak())  # 출력: 바둑이가 짖습니다!

이 예제에서 Dog 클래스는 Animal 클래스를 상속받아 정의되었습니다. Dog 클래스는 부모 클래스의 speak 메서드를 재정의(오버라이드)하여 자신만의 동작을 정의합니다.

8.6 다중 상속 (Multiple Inheritance)

파이썬은 다중 상속을 지원하며, 하나의 클래스가 여러 부모 클래스로부터 속성과 메서드를 상속받을 수 있습니다.

  • 다중 상속 예제:
class A:
    def method_a(self):
        return "A 클래스의 메서드"
 
class B:
    def method_b(self):
        return "B 클래스의 메서드"
 
class C(A, B):
    pass
 
obj = C()
print(obj.method_a())  # 출력: A 클래스의 메서드
print(obj.method_b())  # 출력: B 클래스의 메서드

클래스 CAB를 상속받아 두 클래스의 메서드를 모두 사용할 수 있습니다.

알겠습니다! 8.7, 8.8, 8.9의 내용을 더욱 자세히 설명하겠습니다.

8.7 다형성 (Polymorphism)

다형성은 객체 지향 프로그래밍의 중요한 개념 중 하나로, 서로 다른 클래스의 객체들이 동일한 메서드나 속성을 공유할 수 있도록 하는 것을 의미합니다. 이를 통해 동일한 이름의 메서드를 여러 클래스에서 정의하더라도 각 클래스가 고유한 방식으로 그 메서드를 구현할 수 있습니다. 다형성은 코드의 유연성을 높여주며, 다양한 객체가 동일한 인터페이스를 구현하도록 유도합니다.

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

메서드 오버라이딩은 자식 클래스에서 부모 클래스의 메서드를 재정의(오버라이드)하는 것입니다. 이를 통해 자식 클래스는 부모 클래스의 기본 동작을 재정의하여 고유한 동작을 구현할 수 있습니다.

  • 예제:
class Animal:
    def speak(self):
        return "소리를 냅니다."
 
class Dog(Animal):
    def speak(self):
        return "멍멍!"
 
class Cat(Animal):
    def speak(self):
        return "야옹!"
 
def make_animal_speak(animal):
    print(animal.speak())
 
dog = Dog()
cat = Cat()
 
make_animal_speak(dog)  # 출력: 멍멍!
make_animal_speak(cat)  # 출력: 야옹!

이 예제에서 DogCat 클래스는 부모 클래스인 Animal 클래스의 speak 메서드를 오버라이드하여 각각 다른 동작을 정의합니다. make_animal_speak 함수는 Animal 타입의 객체를 받아 speak 메서드를 호출하지만, 객체의 타입에 따라 서로 다른 결과가 출력됩니다. 이를 통해 동일한 인터페이스(메서드 이름 speak)를 사용하더라도, 각 객체가 고유한 방식으로 동작할 수 있습니다.

8.7.2 다형성과 인터페이스

파이썬은 강력한 다형성을 제공하며, 이를 통해 다양한 타입의 객체를 동일한 방식으로 처리할 수 있습니다. 인터페이스는 객체가 특정 메서드 집합을 구현하도록 강제하는 추상적인 개념입니다. 파이썬에서는 명시적인 인터페이스가 없지만, 다형성을 통해 비슷한 효과를 얻을 수 있습니다.

  • 예제:
class Bird:
    def fly(self):
        return "새가 날아갑니다."
 
class Airplane:
    def fly(self):
        return "비행기가 날아갑니다."
 
class Superhero:
    def fly(self):
        return "슈퍼히어로가 날아갑니다."
 
def make_it_fly(flyer):
    print(flyer.fly())
 
bird = Bird()
airplane = Airplane()
superhero = Superhero()
 
make_it_fly(bird)       # 출력: 새가 날아갑니다.
make_it_fly(airplane)   # 출력: 비행기가 날아갑니다.
make_it_fly(superhero)  # 출력: 슈퍼히어로가 날아갑니다.

이 예제에서 Bird, Airplane, Superhero 클래스는 모두 fly 메서드를 구현하고 있습니다. make_it_fly 함수는 fly 메서드를 호출하는 인터페이스 역할을 하며, 객체의 타입에 관계없이 동일한 방식으로 메서드를 호출할 수 있습니다. 이는 파이썬의 다형성이 어떻게 작동하는지를 잘 보여줍니다.

8.8 캡슐화 (Encapsulation)

캡슐화는 객체 지향 프로그래밍의 또 다른 핵심 개념으로, 객체의 내부 구현을 외부로부터 숨기고, 필요한 인터페이스만 외부에 공개하는 것을 의미합니다. 캡슐화를 통해 객체의 데이터를 보호하고, 외부에서의 직접적인 접근을 제어할 수 있습니다. 이를 통해 코드의 복잡성을 줄이고, 유지보수성을 높일 수 있습니다.

8.8.1 접근 제어자

파이썬에서는 명시적인 접근 제어자(public, protected, private)를 제공하지 않지만, 암묵적인 규칙을 통해 이를 표현할 수 있습니다.

  • 공개 속성 (Public Attributes)

    • 이름이 밑줄(_)로 시작하지 않는 속성은 공개 속성으로 간주됩니다.
    • 클래스 외부에서 직접 접근할 수 있습니다.
    • 예제: self.name = "John"
  • 비공개 속성 (Protected Attributes)

    • 이름이 밑줄(_)로 시작하는 속성은 비공개 속성으로 간주됩니다.
    • 이는 암묵적으로 외부에서 접근하지 않도록 권장하지만, 실제로는 접근이 가능합니다.
    • 예제: self._balance = 1000
  • 완전 비공개 속성 (Private Attributes)

    • 이름이 밑줄 두 개(__)로 시작하는 속성은 완전 비공개 속성으로 간주됩니다.
    • 클래스 외부에서는 접근할 수 없으며, 클래스 내부에서만 접근 가능합니다.
    • 파이썬은 이러한 속성의 이름을 맹글링(name mangling)하여 클래스 외부에서의 접근을 방지합니다.
    • 예제: self.__account_number = "123-456"

8.8.2 접근자와 설정자 (Getters and Setters)

캡슐화를 구현하는 일반적인 방법 중 하나는 접근자(getter)와 설정자(setter) 메서드를 사용하는 것입니다. 이를 통해 외부에서 속성에 안전하게 접근하거나 수정할 수 있습니다.

  • 예제:
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # 완전 비공개 속성
 
    def get_balance(self):
        return self.__balance
 
    def set_balance(self, amount):
        if amount >= 0:
            self.__balance = amount
        else:
            print("잘못된 금액입니다.")
 
account = BankAccount(1000)
print(account.get_balance())  # 출력: 1000
 
account.set_balance(500)
print(account.get_balance())  # 출력: 500
 
account.set_balance(-100)  # 출력: 잘못된 금액입니다.

이 예제에서 __balance는 완전 비공개 속성으로, 클래스 외부에서 직접 접근할 수 없습니다. 대신 get_balanceset_balance 메서드를 통해 안전하게 접근하고 수정할 수 있습니다.

8.8.3 캡슐화의 장점

  • 데이터 보호: 객체의 내부 데이터를 보호하여 외부에서 직접 수정하거나 손상시키는 것을 방지합니다.
  • 유지보수성 향상: 외부에 노출되는 인터페이스를 제한함으로써 코드 변경 시 영향을 최소화할 수 있습니다.
  • 모듈화: 클래스 내부의 구현을 숨기고, 인터페이스를 통해 상호작용하도록 하여 코드의 모듈화를 촉진합니다.

8.9 클래스와 객체의 활용

객체 지향 프로그래밍의 주요 목표 중 하나는 현실 세계의 개념을 코드로 모델링하여 재사용 가능하고 유지보수하기 쉬운 소프트웨어를 만드는 것입니다. 이를 위해 클래스와 객체를 적절히 활용하는 것이 중요합니다. 이번 절에서는 클래스와 객체를 활용한 실용적인 예제를 통해 객체 지향 프로그래밍의 장점을 설명합니다.

8.9.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
            print(f"{amount}원이 입금되었습니다. 현재 잔액: {self.__balance}원")
        else:
            print("입금액은 0보다 커야 합니다.")
 
    def withdraw(self, amount):
        if amount > 0 and amount <= self.__balance:
            self.__balance -= amount
            print(f"{amount}원이 출금되었습니다. 현재 잔액: {self.__balance}원")
        else:
            print("잔액이 부족하거나 잘못된 금액입니다.")
 
    def get_balance(self):
        return self.__balance
 
# 객체 생성 및 메서드 호출
account = BankAccount("홍길동", 1000)
account.deposit(500)
account.withdraw(300)
print(account.get_balance())  # 출력: 1200

이 예제에서 BankAccount 클래스는 계좌 소유자(owner)와 잔액

(__balance)을 속성으로 가지며, 입금(deposit), 출금(withdraw), 잔액 조회(get_balance)와 같은 메서드를 제공합니다. 이를 통해 사용자는 계좌의 상태를 안전하게 관리할 수 있습니다.

8.9.2 예제: 학생 관리 시스템

이 예제에서는 학생의 정보를 관리하는 시스템을 만들어 봅니다. 이 시스템은 학생의 이름, 학번, 성적을 저장하고, 성적을 조회하고 수정하는 기능을 제공합니다.

  • 클래스 정의:
class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.grades = {}
 
    def add_grade(self, subject, grade):
        self.grades[subject] = grade
 
    def get_grade(self, subject):
        return self.grades.get(subject, "해당 과목의 성적이 없습니다.")
 
    def get_gpa(self):
        if not self.grades:
            return "성적이 없습니다."
        return sum(self.grades.values()) / len(self.grades)
 
# 객체 생성 및 메서드 호출
student = Student("이몽룡", "20230001")
student.add_grade("수학", 95)
student.add_grade("영어", 88)
print(student.get_grade("수학"))  # 출력: 95
print(student.get_gpa())  # 출력: 91.5

이 예제에서 Student 클래스는 학생의 이름(name), 학번(student_id), 성적(grades)을 속성으로 가지며, 성적 추가(add_grade), 성적 조회(get_grade), 평점 계산(get_gpa) 메서드를 제공합니다. 이를 통해 사용자는 학생의 학업 성과를 효율적으로 관리할 수 있습니다.

8.9.3 객체 지향 프로그래밍의 장점

  • 재사용성: 클래스는 한 번 정의해 두면 여러 번 재사용할 수 있으며, 상속을 통해 기존 클래스를 확장하여 새로운 기능을 추가할 수 있습니다.
  • 유지보수성: 코드를 모듈화하고 캡슐화하여 변경 사항이 발생해도 영향을 최소화할 수 있습니다.
  • 추상화: 복잡한 시스템을 단순화하여 핵심적인 부분에 집중할 수 있습니다. 예를 들어, 학생 관리 시스템에서는 학생 객체를 추상화하여, 학생의 이름, 학번, 성적 등 중요한 정보만을 다루도록 합니다.
  • 확장성: 새로운 기능을 쉽게 추가하거나 기존 기능을 확장할 수 있습니다. 예를 들어, 은행 시스템에 새로운 계좌 유형(예: 저축 계좌, 당좌 계좌)을 추가하고 싶다면, 기존 BankAccount 클래스를 상속받아 새로운 클래스를 정의할 수 있습니다.

이로써 파이썬에서 객체 지향 프로그래밍의 핵심 개념과 이를 활용하는 방법에 대해 더욱 깊이 이해할 수 있게 되었습니다. 객체 지향 프로그래밍을 통해 소프트웨어를 더 효율적이고 구조적으로 작성할 수 있습니다.

관련 문서

1. 파이썬(Python) 이란 SOILD 원칙 소프트웨어 개발 원칙