디자인 패턴 개요
디자인 패턴은 소프트웨어 개발에서 반복적으로 발생하는 문제에 대한 검증된 해결책으로, 코드의 재사용성과 유지보수성을 높이는 데 기여한다. 일반적으로 23가지로 분류되며, 주로 생성형, 구조형, 행동형 세 가지 유형으로 나뉜다.
주요 패턴 카테고리
- 생성형 패턴 (5종): 팩토리 메서드, 추상 팩토리, 싱글턴, 빌더, 원형
- 구조형 패턴 (7종): 어댑터, 데코레이터, 프록시, 플라크, 브릿지, 컴포지트, 펄프
- 행동형 패턴 (11종): 전략, 템플릿 메서드, 옵저버, 이터레이터, 책임 연쇄, 명령, 백업, 상태, 방문자, 중재자, 인터프리터
싱글턴 패턴의 핵심 개념
한 클래스에 대해 시스템 전체에서 단 하나의 인스턴스만 존재하도록 보장하고, 전역적으로 접근 가능한 포인트를 제공하는 디자인 패턴이다. 특히 자원을 많이 사용하거나 초기화 비용이 큰 객체(예: 데이터베이스 연결, 설정 관리)에 적합하다.
다양한 싱글턴 구현 방법
모듈 수준의 싱글턴 (가장 일반적인 방식)
모듈 내부에서 클래스 인스턴스를 미리 생성하고, 외부에서 해당 모듈을 가져와서 사용함으로써 전역 단일 인스턴스를 보장할 수 있다.
# db_module.py
class Database:
def __init__(self, host):
self.host = host
def query(self, sql):
return f"Executing: {sql} on {self.host}"
db_instance = Database("localhost")
# main.py
from db_module import db_instance
print(db_instance.query("SELECT * FROM users"))
클래스 내부에서 싱글턴 제어
클래스 내부에 `__instance` 속성을 활용하여 인스턴스 생성 여부를 체크하고, 클래스 메서드를 통해 유일한 인스턴스를 반환한다.
class Logger:
__instance = None
def __init__(self, level="INFO"):
self.level = level
@classmethod
def get_instance(cls):
if cls.__instance is None:
cls.__instance = cls()
return cls.__instance
# 사용 예
log1 = Logger.get_instance()
log2 = Logger.get_instance()
print(id(log1) == id(log2)) # True
메타클래스를 통한 싱글턴 제어
메타클래스를 정의하여 클래스 생성 시 자동으로 인스턴스를 생성하고, `__call__`을 오버라이드해 인스턴스 생성 로직을 제어한다.
class SingletonMeta(type):
def __init__(cls, name, bases, attrs):
cls._instance = None
super().__init__(name, bases, attrs)
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class ConfigManager(metaclass=SingletonMeta):
def __init__(self, config_file="app.conf"):
self.config = config_file
config1 = ConfigManager()
config2 = ConfigManager()
print(id(config1) == id(config2)) # True
데코레이터를 활용한 싱글턴 패턴
데코레이터를 사용하여 클래스 호출 시점에 인스턴스 생성을 제어하며, 인자가 없으면 기존 인스턴스를 반환하고, 인자가 있으면 새로운 인스턴스를 생성한다.
def singleton(cls):
instance = None
def wrapper(*args, **kwargs):
nonlocal instance
if not args and not kwargs:
if instance is None:
instance = cls()
return instance
return cls(*args, **kwargs)
return wrapper
@singleton
class CacheManager:
def __init__(self):
self.data = {}
cache1 = CacheManager()
cache2 = CacheManager()
print(id(cache1) == id(cache2)) # True
메타클래스에서 조건부 싱글턴 처리
처음 인자를 받은 경우 그 인스턴스를 싱글턴으로 고정하고, 이후에는 다른 인자를 받더라도 새 인스턴스를 생성하도록 한다.
class ConditionalSingleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, '_single'):
if args or kwargs:
cls._single = super().__call__(*args, **kwargs)
else:
raise RuntimeError("First call must provide arguments")
else:
if args or kwargs:
return super().__call__(*args, **kwargs)
return cls._single
class ResourceManager(metaclass=ConditionalSingleton):
def __init__(self, name):
self.name = name
res1 = ResourceManager("DB")
res2 = ResourceManager("API") # 새로운 인스턴스
res3 = ResourceManager() # 싱글턴 반환
print(res1.name, res2.name, res3.name)