C++20 이상의 모던 C++ 언어 기능들은 개발자들에게 이전에는 상상하기 어려웠던 수준의 표현력, 안전성, 유지보수성을 제공합니다. 한편, 소프트웨어 개발의 전통적인 지식인 “디자인 패턴(Design Patterns)”은 여전히 유지보수성과 확장성이 요구되는 대규모 코드베이스에서 널리 활용되는 개념입니다.
이 시리즈에서는 고전적인 GoF(Gang of Four) 디자인 패턴들을 모던 C++ 스타일로 재구현하며, 기존 C++11/14/17 시대의 구현 방식과 비교 분석할 것입니다. 이를 통해 독자들은 각 패턴의 본질을 재확인하는 동시에, Concepts, Ranges, Coroutines, std::format, std::expected, std::jthread, std::variant, std::string_view 등 모던 C++ 요소들을 디자인 패턴에 어떻게 접목할 수 있는지 알게 될 것입니다.
디자인 패턴이란?
디자인 패턴은 소프트웨어 설계 문제를 해결하기 위해 검증된 반복 가능한 솔루션을 제시하는 “좋은 설계 관습” 모음입니다. 1994년 에릭 감마(Erich Gamma) 등 4인의 저자(Gang of Four)가 소개한 23가지 디자인 패턴은 다음과 같이 세 그룹으로 나뉩니다.
- 생성(Creational) 패턴: 객체 생성 로직을 유연하고 재사용 가능하도록 만드는 패턴 (예: 싱글톤, 빌더, 팩토리 메서드 등)
- 구조(Structural) 패턴: 클래스나 객체를 구조적으로 효율적으로 조합하여 더 큰 시스템을 유연하게 만드는 패턴 (예: 어댑터, 데코레이터, 컴포지트 등)
- 행동(Behavioral) 패턴: 객체 간 책임 분배, 알고리즘 캡슐화, 상호작용 방식을 정의해 시스템의 유연성과 확장성을 높이는 패턴 (예: 옵저버, 전략, 상태 등)
이 패턴들은 객체지향 언어 설계에 초점을 맞추고 있으며, 초기에는 C++98/03, Java, Smalltalk 등 당시 주류였던 OOP 언어에서 구현 사례가 많았습니다.
왜 모던 C++으로 다시 살펴볼까?
C++11 이후 언어는 크게 진화했습니다. 특히 C++20에서는 다음과 같은 특징이 추가·개선되어, 디자인 패턴 구현에서 보다 직관적이고 안전하며 간결한 코드를 작성할 수 있게 되었습니다.
- Concepts: 템플릿 파라미터에 타입 제약을 명확히 걸 수 있어, SFINAE 트릭을 남발하던 시대를 벗어나 템플릿 코드 가독성이 크게 향상.
- Coroutines: 비동기 처리나 상태 머신 패턴을 더 깔끔하게 표현 가능. 옵저버나 명령(Command) 패턴을 비동기적으로 구현하는 데 유용.
- Ranges: 컨테이너를 다루는 로직을 pipeline으로 명확히 표현해, 반복적인 반복자 관리 코드를 줄이고, 데코레이터나 전략 패턴에 pipeline 사고방식을 접목 가능.
- std::format: printf 스타일 형식 지정의 타입 안전한 대안으로, 로깅, 디버깅 메시지에서 깔끔한 문자열 처리를 제공.
- std::expected, std::optional: 에러 처리나 필수/선택 파라미터 처리를 명확히 해, 빌더(Builder)나 팩토리(Factory) 패턴에서 객체 생성 실패나 선택 파라미터 관리가 단순화.
- std::variant, std::visit: 다형성을 가상 함수가 아닌 값 기반 다형성으로 쉽게 표현, 상태(State) 패턴이나 비지터(Visitor) 패턴을 더 직관적으로 구현 가능.
- std::string_view: 문자열 처리 시 불필요한 복사 제거, 효율적인 API 인터페이스 설계에 도움.
- Threads, std::jthread와 stop_token: Observer나 Mediator 패턴에서 비동기 이벤트를 더 안전하게 처리.
이러한 기능들을 활용하면 기존의 디자인 패턴 구현 방식과 비교해 다음과 같은 이점을 얻을 수 있습니다.
- 가독성 향상: Concepts로 인터페이스를 명확히, Ranges로 반복작업을 pipeline 형태로 표현
- 유지보수성 개선: 엄격한 타입 제약과 명확한 에러 처리(std::expected)로 코드를 더 안정적으로 변경 가능
- 성능 최적화: std::string_view나 move semantics, lazy evaluation을 통한 불필요한 복사 제거
- 비동기/병렬 처리 지원 강화: Coroutines와 jthread로 비동기 패턴 구현 용이
앞으로 다룰 내용
이 시리즈에서는 GoF 디자인 패턴 전부(23개)를 대상으로, 다음과 같은 접근을 취합니다.
- 패턴 소개: 각 패턴의 목적, 적용 상황, 전통적 구현 방식(클래스 상속, 가상 함수, raw pointer 활용)을 간략히 정리.
- 기존 C++ 스타일 구현 예시: C++11/14/17 정도 수준으로 구현한 예제 코드 소개.
- 모던 C++20 이상 스타일로 리팩토링: Concepts, Ranges, lambda, std::format, std::expected 등의 기능을 활용해 코드 단순화 및 개선.
- 비교 및 분석: 변경 전후 코드의 차이점, 개선된 부분(가독성, 안전성, 성능), 그리고 모던 C++를 적용하는데 주의할 점 논의.
패턴별로 다음과 같이 섹션을 진행할 예정입니다.
- 생성 패턴: 싱글톤, 빌더, 팩토리 메서드, 추상 팩토리, 프로토타입
- 구조 패턴: 어댑터, 브리지, 컴포지트, 데코레이터, 퍼사드, 플라이웨이트, 프록시
- 행동 패턴: 책임 연쇄, 커맨드, 인터프리터, 이터레이터, 중재자, 메멘토, 옵저버, 상태, 전략, 템플릿 메서드, 비지터
각 패턴 구현 예제는 가능한 한 실용적이고 직관적인 예제를 들어 설명하며, 깃허브나 Gist에 코드 예시를 제공할 수도 있습니다.
마무리
이 시리즈를 통해 독자들은 클래식한 디자인 패턴을 다시 복습하면서, 모던 C++ 언어 기능을 어떻게 패턴 구현에 적용할 수 있는지 배우게 될 것입니다. 궁극적으로 이 시리즈는 "디자인 패턴"이라는 소프트웨어 설계 유산과 "모던 C++"이라는 언어 발전이 만나, 더 생산적이고 안정적인 소프트웨어를 만들 수 있다는 점을 보여주는 좋은 실례가 될 것입니다.
다음 글에서는 **[싱글톤(Singleton) 패턴]**부터 시작해서, 전통적인 싱글톤 구현과 모던 C++를 활용한 안전하고 명확한 싱글톤 구현 방법을 비교해보겠습니다.