[파이썬] 클래스 디자인과 캡슐화

캡슐화(encapsulation)는 객체 지향 프로그래밍의 핵심 개념 중 하나로, 데이터와 해당 데이터를 다루는 메서드를 하나의 단위로 묶는 것을 의미합니다. 캡슐화는 코드의 가독성과 유지보수성을 높이고, 코드의 재사용성을 증가시킵니다. 파이썬은 클래스(class)를 사용하여 캡슐화를 구현할 수 있으며, 클래스 디자인(class design)은 캡슐화를 활용하여 객체 지향 프로그램을 구조화하는 과정을 말합니다.

파이썬에서 클래스를 디자인하는 과정에서는 몇 가지 핵심 원칙을 고려해야 합니다. 이 글에서는 클래스 디자인과 캡슐화를 위한 몇 가지 중요한 팁과 예제 코드를 소개하겠습니다.

1. 단일 책임 원칙을 고려하라 (Single Responsibility Principle)

클래스는 단일 책임 원칙에 따라 단 하나의 주된 역할을 수행해야 합니다. 클래스가 여러 가지 역할을 동시에 수행한다면 클래스의 목적을 파악하기 어렵게 되고, 코드를 이해하고 유지보수하기 어려워집니다. 클래스가 책임을 분리해야 할 때는 다른 클래스를 만들어 관련 기능을 분리하는 것을 고려해야 합니다.

class Car:
    def __init__(self, color, brand):
        self.color = color
        self.brand = brand
    
    def start_engine(self):
        # 엔진을 가동하는 로직
    
    def stop_engine(self):
        # 엔진을 정지하는 로직

class CarMaintenance:
    def __init__(self, car):
        self.car = car
    
    def change_oil(self):
        # 기름 교체 로직
    
    def replace_tires(self):
        # 타이어 교체 로직

위 예제에서 Car 클래스는 자동차의 주요 기능을 담당합니다. 이 때문에 Car 클래스는 start_engine 메서드와 stop_engine 메서드를 가지고 있습니다. 반면, CarMaintenance 클래스는 자동차의 정비와 관련된 기능을 담당합니다. 따라서 change_oil 메서드와 replace_tires 메서드를 가지고 있습니다. 이렇게 관련 기능을 분리하면 클래스의 역할과 책임이 명확해지므로 코드를 이해하고 수정하기가 더 쉬워집니다.

2. 정보 은닉을 실천하라 (Information Hiding)

클래스 디자인에 있어서 정보 은닉은 중요한 개념입니다. 정보 은닉은 객체의 내부 구현 세부 사항을 외부에 감추는 것을 의미합니다. 이를 통해 클래스의 사용자는 해당 클래스의 기능을 알고 사용할 수 있지만, 내부 동작 방식과 데이터 구조에 대해서는 알 필요가 없습니다. 정보 은닉을 통해 클래스의 내부 구현을 변경하더라도 외부 코드에 영향을 주지 않고 동작을 유지할 수 있습니다.

class Rectangle:
    def __init__(self, width, height):
        self._width = width
        self._height = height
    
    def get_area(self):
        return self._width * self._height

    def set_dimensions(self, width, height):
        self._width = width
        self._height = height

rect = Rectangle(5, 10)
area = rect.get_area()
print(area)  # 50

rect.set_dimensions(3, 7)
new_area = rect.get_area()
print(new_area)  # 21

위 예제에서 _width_height 변수는 언더스코어(_)로 시작하는데, 이는 해당 변수들이 클래스 외부에서 직접적으로 접근되지 말아야 함을 의미합니다. 대신, get_area 메서드를 통해 클래스 내부의 데이터를 다룹니다. 이렇게 하면 Rectangle 클래스의 내부 구현을 수정해도 외부 코드에 영향을 주지 않고 정상적으로 동작할 수 있습니다.

3. 상속과 인터페이스를 활용하라 (Inheritance and Interfaces)

클래스 디자인에서 상속과 인터페이스는 다형성(polymorphism)을 구현하는 데 큰 역할을 합니다. 상속은 이미 작성된 클래스의 기능을 재사용하고 확장하는 데 사용되며, 인터페이스는 클래스 사이의 통신과 협력을 위한 규약을 제공합니다. 이를 통해 코드의 재사용성과 유연성을 높일 수 있습니다.

class Animal:
    def sound(self):
        pass

class Dog(Animal):
    def sound(self):
        return "Woof!"

class Cat(Animal):
    def sound(self):
        return "Meow!"

animals = [Dog(), Cat()]
for animal in animals:
    print(animal.sound())

위 예제에서 Animal 클래스는 단순히 기본적인 sound 메서드를 정의합니다. Dog 클래스와 Cat 클래스는 Animal 클래스를 상속받아 각각 sound 메서드를 재정의합니다. 이렇게 하면 Dog 객체와 Cat 객체 모두 sound 메서드를 호출할 수 있으며, 다형성을 통해 원하는 동작을 얻을 수 있습니다.

이렇게 클래스 디자인과 캡슐화를 적절히 활용하면 코드의 가독성과 유지보수성을 향상시킬 수 있습니다. 단일 책임 원칙을 준수하고 정보 은닉을 지키며 상속과 인터페이스를 적절히 활용하는 것이 좋은 클래스 디자인의 핵심입니다.