[Python] 디자인 패턴 사용 방법

Python은 배우기 쉽고 유연하며 강력한 매우 인기 있는 프로그래밍 언어로, 다양한 분야에서 널리 사용됩니다. 그러나 많은 사람들은 Python이 절차 지향 언어이며 객체 지향 프로그래밍 스타일을 잘 지원하지 못한다고 생각합니다.

이 생각은 잘못된 것입니다. Python은 객체 지향 프로그래밍을 지원할 뿐만 아니라 디자인 패턴도 효과적으로 적용할 수 있습니다.

디자인 패턴이란 무엇인가?

디자인 패턴(Design Pattern)은 널리 인정받고 검증된 프로그래밍 경험의 집합입니다. 이는 다양한 프로그래밍 시나리오에서 적용할 수 있는 일반적인 솔루션을 제공합니다. 디자인 패턴의 등장은 코드 재사용, 시스템 확장성, 코드 가독성 등 소프트웨어 개발의 일반적인 문제를 해결하기 위함입니다.

디자인 패턴을 사용하는 이유는?

디자인 패턴을 사용하는 이점은 다음과 같습니다:

  • 코드 재사용: 디자인 패턴을 사용하면 코드를 분해하고 조합하여 코드 재사용을 달성할 수 있습니다.
  • 시스템 확장성: 디자인 패턴은 시스템을 더 유연하게 만들고, 확장이 용이하며, 다양한 요구에 적응할 수 있게 합니다.
  • 코드 가독성: 디자인 패턴을 사용하면 코드의 가독성을 높여 코드가 더 명확해집니다.

파이썬에서의 디자인 패턴

파이썬에서의 디자인 패턴은 다른 언어에서의 디자인 패턴과 유사하지만, 몇 가지 차이점이 있습니다. 파이썬에서의 디자인 패턴은 크게 세 가지로 나눌 수 있습니다: 생성 패턴, 구조 패턴, 행동 패턴.

이번 글에서는 몇 가지 일반적인 패턴을 설명합니다.

1. 팩토리 패턴 (Factory Pattern)

팩토리 패턴은 생성 패턴으로, 객체를 생성하는 가장 좋은 방법을 제공합니다. 이 패턴은 객체의 생성과 사용을 분리하여 객체 생성을 더 유연하게 만듭니다.

파이썬에서 팩토리 패턴을 사용하여 다양한 객체를 생성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Dog:
def __init__(self):
self.name = "dog"

class Cat:
def __init__(self):
self.name = "cat"

class AnimalFactory:
def create_animal(self, animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
else:
return None

factory = AnimalFactory()
animal = factory.create_animal("dog")
print(animal.name)

# 출력: dog

2. 싱글톤 패턴 (Singleton Pattern)

싱글톤 패턴은 클래스에 하나의 인스턴스만 존재하도록 보장하며, 전역 접근 지점을 제공합니다.

파이썬에서는 데코레이터를 사용하여 싱글톤 패턴을 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton:
__instance = None

def __new__(cls):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls.__instance

a = Singleton()
b = Singleton()

print(a is b)

# 출력: True

3. 어댑터 패턴 (Adapter Pattern)

어댑터 패턴은 구조 패턴으로, 클래스의 인터페이스를 클라이언트가 원하는 다른 인터페이스로 변환합니다.

파이썬에서는 어댑터 패턴을 사용하여 호환되지 않는 인터페이스 간의 호환성을 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Target:
def request(self):
pass

class Adaptee:
def specific_request(self):
pass

class Adapter(Target):
def __init__(self, adaptee):
self.adaptee = adaptee

def request(self):
self.adaptee.specific_request()

adaptee = Adaptee()
adapter = Adapter(adaptee)
adapter.request()

4. 데코레이터 패턴 (Decorator Pattern)

데코레이터 패턴은 구조 패턴으로, 객체에 새로운 동작을 동적으로 추가할 수 있습니다.

파이썬에서는 데코레이터 함수를 사용하여 함수나 클래스의 동작을 수정할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
def logging(func):
def wrapper(*args, **kwargs):
print("call function:", func.__name__)
return func(*args, **kwargs)
return wrapper

@logging
def foo():
print("hello world")

foo()

# 출력: call function: foo hello world

5. 옵저버 패턴 (Observer Pattern)

옵저버 패턴은 행동 패턴으로, 객체 간 일대다 관계를 정의하여, 객체의 상태가 변경될 때 이를 의존하는 모든 객체가 자동으로 업데이트됩니다.

파이썬에서는 옵저버 패턴을 사용하여 이벤트 중심의 프로그래밍을 구현할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Subject:
def __init__(self):
self.observers = []

def attach(self, observer):
self.observers.append(observer)

def detach(self, observer):
self.observers.remove(observer)

def notify(self):
for observer in self.observers:
observer.update(self)

class Observer:
def update(self, subject):
pass

class ConcreteSubject(Subject):
def __init__(self):
super().__init__()
self.state = 0

def get_state(self):
return self.state

def set_state(self, state):
self.state = state
self.notify()

class ConcreteObserver(Observer):
def update(self, subject):
print("state changed to:", subject.get_state())

subject = ConcreteSubject()
observer = ConcreteObserver()
subject.attach(observer)
subject.set_state(1)

# 출력: state changed to: 1

6. 빌더 패턴 (Builder Pattern)

빌더 패턴은 복잡한 객체의 생성 과정을 그 표현과 분리하여 동일한 생성 과정으로 다양한 표현을 만들 수 있게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Director:
def construct_car(self, builder):
builder.create_new_car()
builder.add_model()
builder.add_engine()

class Builder:
def __init__(self):
self.car = None

def create_new_car(self):
self.car = Car()

def get_car(self):
return self.car

class CarBuilder(Builder):
def add_model(self):
self.car.model = "Sports Car"

def add_engine(self):
self.car.engine = "V8"

class Car:
def __init__(self):
self.model = None
self.engine = None

director = Director()
car_builder = CarBuilder()

director.construct_car(car_builder)
car = car_builder.get_car()

print(f"Car Model: {car.model}, Engine: {car.engine}")

7. 전략 패턴 (Strategy Pattern)

전략 패턴은 알고리즘 군을 정의하고, 이를 캡슐화하며, 이 알고리즘을 상호 교체 가능하게 만드는 패턴입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class PaymentContext:
def __init__(self, payment_strategy):
self.payment_strategy = payment_strategy

def execute_payment(self, amount):
return self.payment_strategy.pay(amount)

class CreditCardPayment:
def pay(self, amount):
return f"Paying ${amount} using Credit Card."

class PayPalPayment:
def pay(self, amount):
return f"Paying ${amount} using PayPal."

credit_card_payment = CreditCardPayment()
paypal_payment = PayPalPayment()

context_credit_card = PaymentContext(credit_card_payment)
context_paypal = PaymentContext(paypal_payment)

print(context_credit_card.execute_payment(100))
print(context_paypal.execute_payment(50))

8. 책임 연쇄 패턴 (Chain of Responsibility Pattern)

책임 연쇄 패턴은 요청을 처리할 수 있는 여러 핸들러가 체인으로 연결되어 각 핸들러가 요청을 처리할지 또는 다음 핸들러로 전달할지 결정하는 패턴입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Handler:
def __init__(self, successor=None):
self.successor = successor

def handle_request(self, request):
if self.successor:
self.successor.handle_request(request)

class ConcreteHandlerA(Handler):
def handle_request(self, request):
if request == "A":
print("Handler A processing request A.")
else:
super().handle_request(request)

class ConcreteHandlerB(Handler):
def handle_request(self, request):
if request == "B":
print("Handler B processing request B.")
else:
super().handle_request(request)

class Client:
def __init__(self):
self.handler_chain = ConcreteHandlerA(ConcreteHandlerB())

def make_request(self, request):
self.handler_chain.handle_request(request)

client = Client()
client.make_request("A")
client.make_request("B")

요약

Python은 절차 지향 언어일 뿐만 아니라 객체 지향 프로그래밍 스타일도 지원합니다. 디자인 패턴을 사용하면 Python의 객체 지향 기능을 더 잘 활용하여 코드를 더 유연하고, 읽기 쉽고, 유지보수가 용이하게 만들 수 있습니다.

파이썬에서의 디자인 패턴에는 팩토리 패턴, 싱글톤 패턴, 어댑터 패턴, 데코레이터 패턴, 옵저버 패턴 등이 포함됩니다. 이러한 패턴들은 다양한 프로그래밍 시나리오에 적용할 수 있으며, 소프트웨어 개발의 공통적인 문제를 해결하는 데 도움을 줍니다.

Share