디자인 패턴이란?
디자인 패턴이란, 소프트웨어 개발에서 반복적으로 발생하는 문제에 대한 해결책을 제공하는 일종의 템플릿이다.
이러한 패턴들은 개발자들이 공통된 문제를 해결하기 위해 검증된 방법을 활용할 수 있도록 도와준다.
디자인 패턴은 특정한 상황에서 특정한 목적을 달성하기 위해 소프트웨어 디자인을 조직화하고 구조화하는 방법을 제시한다.
* 디자인 패턴의 장점
디자인 패턴을 사용하면 유지보수가 용이하고, 코드 가독성, 코드 재사용성, 확장성이 향상되는 등 다양한 장점이 있다.
* 디자인 패턴의 종류
싱글턴 패턴(Singleton Pattern), 멀티턴 패턴(Multiton Pattern), 반복자 패턴(Iterator Pattern), 팩토리 패턴(Factory Pattern) 등 다양한 디자인 패턴이 존재한다.
싱글턴 패턴(Singloeton Pattern)
디자인 패턴에는 대표적으로 싱글턴 패턴(Singleton Pattern)이 있다.
어떤 클래스가 최대 하나의 인스턴스를 가질 수 있도록 하는 패턴이다.
이는 어떤 시스템에서 특정 클래스의 인스턴스가 오직 하나만 필요할 때 유용하다.
Python에서 싱글턴 패턴을 사용하여 클래스를 구현해보자.
class MyClass:
obj = None # 객체를 캐싱할 변수
def __init__(self):
if MyClass.obj: # 객체가 존재하다면
raise Exception("instance already exists")
MyClass.obj = self
pass
@classmethod
def get_instance(cls):
if cls.obj is None:
MyClass()
return cls.obj
위에서 언급했듯, 싱글턴 패턴에서는 클래스가 최대 하나의 객체만 가지도록 제한한다.
그래서 MyClass 클래스에서 유일한 객체를 담을 클래스 변수 obj를 선언하고 __init__ 함수에서 obj에 유일한 객체를 담았다. obj에 이미 객체가 담겨있다면 예외(exception)를 발생시킨다. 위 코드는 예외를 발생시키기 때문에 get_instance() 함수를 따로 만들어줘야 한다는 단점이 있다.
하지만 __new__() 스페셜 메서드를 사용한다면 예외를 발생시키지 않고도 구현할 수 있다.
class MyClass:
obj = None # 객체를 캐싱할 변수
def __new__(cls):
if cls.obj is None:
cls.obj = object.__new__(cls)
return cls.obj
obj1 = MyClass()
obj2 = MyClass()
print(id(obj1))
print(id(obj2))
>> 2412137173760
>> 2412137173760
obj1과 obj2가 정확히 같은 객체를 가르키는 것을 확인할 수 있다.
반복자 패턴(Iterator Pattern)
반복자 패턴은 행동 패턴 중 하나로, 반복 가능한 컬렉션(예: 리스트, 배열 등)의 요소에 순차적으로 접근하는 방법을 제공한다. 반복자 패턴은 컬렉션의 내부 구조를 노출시키지 않으면서도 요소에 접근할 수 있는 인터페이스를 제공하여 사용자에게 편의를 제공한다.
반복자 패턴을 이용하여 연결 리스트(Linked List)를 구현해보자.
class LinkedList:
class Node:
def __init__(self, data, next):
self.data = data
self.next = next
pass
def __init__(self):
self.head = None
self.count = 0
self.cursor = 0 # 현재 방문한 노드의 인덱스
pass
def insert_head(self, data):
self.head = LinkedList.Node(data, self.head)
self.count += 1
pass
def get(self, idx):
if (self.count == 0) or (idx >= self.count):
return None
cur_node = self.head
for _ in range(idx):
cur_node = cur_node.next
return cur_node.data
def __len__(self):
return self.count
# 현재 노드의 값을 반환하고 다음 노드로 이동하는 메서드
def next(self):
if self.cursor == self.count
raise StopIteration
data = self.get(self.cursor)
self.cursor += 1
return data
my_list = LinkedList()
for item in range(5):
my_list.insert_head(item+1)
for idx in range(len(my_list)):
print(my_list.get(idx), end=" ")
print()
while my_list.has_next():
print(my_list.next(), end=" ")
위 코드는 객체 변수에 cursor를 선언하여 방문한 노드의 인덱스를 기록하였다.
그리고 next()라는 인스턴스 메서드를 제공하여 사용자로 하여금 요소를 순회하기 용이하게 한다.
가장 마지막 노드에서 next() 메서드가 호출될 경우, StopIteration이라는 예외(Exception)이 발생한다.
StopIteration은 반복자(iterator) 객체가 더 이상 반환할 요소가 없음을 나타내는 예외 클래스다.
주로 next() 메서드와 같이 이용되는 예외다.
데코레이터 패턴(Decorator Pattern)
데코레이터 패턴은 구조 패턴 중 하나로 주어진 상황 및 용도에 따라 어떤 객체에 기능을 확장하기 위해 사용되는 패턴이다.
아래 코드를 보자.
def add_hello(method):
def wrapper():
return f"Hello, {method()}"
return wrapper
def add_goodbye(method):
def wrapper():
return f"{method()}, Bye Bye"
return wrapper
@add_hello # greeting = add_hello(greeting)
@add_goodbye # greeting = add_goodbye(greeting)
def bit():
return "My name is Bit."
print(introduce())
>> Hello, My name is Bit., Bye Bye
introduce()라는 함수에 @add_hello, @add_goodbye라는 데코레이터를 추가하여 introduce()라는 함수의 원본을 건드리지 않고 "Hello"와 "Bye"를 쉽게 추가할 수 있다.
이제 byte() 함수를 아래와 같이 추가한다고 해보자.
def byte():
return "My name is Byte."
위와 같은 함수를 여러개 추가하더라도 매번 Hello와 ByeBye가 붙은 파생 함수를 여러개 구현할 필요없이 단순히 데코레이터만 붙이면 간단하게 Hello와 Bye Bye를 쉽게 덧붙일 수 있다!
'Python' 카테고리의 다른 글
Python 제너레이터(Generator) (0) | 2024.03.29 |
---|---|
Python 연결 리스트(Linked List) (0) | 2024.03.29 |
Python 스페셜 메서드 len, getitem, iter, str, repr, new (0) | 2024.03.28 |
Python 클래스, 객체 (0) | 2024.03.27 |
Python 함수 선언 방법, 변수 지역성 (0) | 2024.03.27 |