캡슐화(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
메서드를 호출할 수 있으며, 다형성을 통해 원하는 동작을 얻을 수 있습니다.
이렇게 클래스 디자인과 캡슐화를 적절히 활용하면 코드의 가독성과 유지보수성을 향상시킬 수 있습니다. 단일 책임 원칙을 준수하고 정보 은닉을 지키며 상속과 인터페이스를 적절히 활용하는 것이 좋은 클래스 디자인의 핵심입니다.