Unisquads
로그인
홈
이용약관·개인정보처리방침·콘텐츠정책·© 2026 Unisquads
이용약관·개인정보처리방침·콘텐츠정책
© 2026 Unisquads. All rights reserved.

디자인 패턴 (r1)

이 문서의 과거 버전 (r1)을 보고 있습니다. 수정일: 2026.02.14 21:42

디자인 패턴

분류

소프트웨어 공학

목적

재사용 가능한 설계 솔루션

주요 유형

생성 패턴, 구조 패턴, 행동 패턴

대표적 예시

싱글톤 패턴, 팩토리 메서드 패턴, 옵저버 패턴

기원

1970년대 크리스토퍼 알렉산더의 건축 이론

소프트웨어 도입

1994년 GoF (Gang of Four)의 저서 'Design Patterns'

상세 정보

정의

소프트웨어 설계에서 자주 발생하는 문제에 대한 일반적이고 재사용 가능한 해결책

핵심 요소

패턴명, 문제, 해결책, 결과

주요 이점

코드 재사용성 증가, 유지보수성 향상, 개발자 간 의사소통 효율화

단점

과도한 사용 시 설계 복잡도 증가, 초기 학습 곡선

적용 분야

객체지향 프로그래밍, 엔터프라이즈 애플리케이션, 게임 개발

관련 개념

객체지향 설계 원칙 (SOLID), 아키텍처 패턴, 리팩토링

주요 저서

'Design Patterns: Elements of Reusable Object-Oriented Software' (GoF)

언어별 구현

Java, C++, Python, JavaScript 등 대부분의 객체지향/다중 패러다임 언어

[[GoF]] 패턴 수

23가지

현대적 발전

디자인 패턴의 진화, 마이크로서비스 패턴, 반응형 프로그래밍 패턴

1. 개요

디자인 패턴은 소프트웨어 설계에서 반복적으로 발생하는 문제에 대한 일반적이고 재사용 가능한 해결책이다. 이는 특정 프로그래밍 언어나 기술에 종속되지 않는 설계의 경험과 지혜를 정리한 것이다. 패턴은 완성된 코드가 아니라, 문제를 해결하기 위한 템플릿이나 청사진 역할을 한다. 개발자들은 이를 참조하여 자신의 상황에 맞게 구현을 구체화한다.

디자인 패턴의 주요 목적은 효율적인 의사소통과 설계 품질 향상이다. 패턴은 설계 아이디어를 전달하는 공통 언어를 제공한다. 예를 들어, "여기에는 옵저버 패턴을 적용하자"라고 말하면 복잡한 설명 없이도 객체 간의 일대다 종속 관계 설계를 명확히 전달할 수 있다. 또한, 검증된 구조를 재사용함으로써 유연하고 확장 가능하며 유지보수가 쉬운 소프트웨어를 만드는 데 기여한다.

디자인 패턴은 주로 객체지향 프로그래밍과 밀접한 관련이 있지만, 그 적용 범위는 더 넓다. 패턴은 특정 문제의 핵심을 파악하고, 해결책의 구성 요소와 그들 간의 관계, 협력 방식을 설명한다. 각 패턴은 적용 동기, 문제, 해결책, 결과 및 트레이드오프에 대한 체계적인 설명을 포함한다. 이는 단순히 코드를 복사해 붙여넣는 것이 아니라, 설계 원칙과 모범 사례를 학습하고 적용하는 데 초점을 맞춘다.

2. 디자인 패턴의 역사와 배경

디자인 패턴의 개념은 원래 건축 분야에서 비롯되었다. 1977년, 건축가 크리스토퍼 알렉산더는 그의 저서 《영원한 건축 방식》에서 건축과 도시 계획에서 반복적으로 발생하는 문제에 대한 해결책을 '패턴'이라는 형태로 체계화하였다[1]. 그는 각 패턴이 특정 문맥에서 발생하는 문제를 해결하기 위한 검증된 설계 원칙이라고 설명했다. 이 아이디어는 소프트웨어 공학 분야에 큰 영감을 주었다.

소프트웨어 공학 분야로의 본격적인 도입은 1987년 켄트 벡과 워드 커닝햄이 객체지향 프로그래밍에서의 경험을 패턴 언어로 정리하면서 시작되었다. 이후 1994년, 에리히 감마, 리처드 헬름, 랄프 존슨, 존 블리시디스 네 명의 저자가 공동 집필한 《디자인 패턴》[2] 책이 출간되면서 소프트웨어 디자인 패턴이 널리 알려지게 되었다. 이들은 흔히 'Gang of Four'(GoF)라고 불리며, 그들의 책은 23개의 고전적인 객체지향 디자인 패턴을 정리한 기준이 되었다.

이 책의 출간은 소프트웨어 설계에 혁신적인 변화를 가져왔다. 개발자들은 특정 설계 문제에 대해 매번 처음부터 해결책을 고민하기보다, 검증된 패턴을 적절히 적용하여 효율적이고 유지보수 가능한 코드를 작성할 수 있게 되었다. 이로 인해 디자인 패턴은 소프트웨어 개발의 필수적인 공통 언어이자 지식 체계로 자리 잡았다.

3. 디자인 패턴의 분류

디자인 패턴은 일반적으로 해결하려는 문제의 성격에 따라 세 가지 주요 범주로 분류된다. 이 분류는 패턴의 목적, 즉 객체를 생성하는지, 객체들의 구조를 구성하는지, 혹은 객체들 간의 상호작용과 책임 분배를 다루는지에 기반을 둔다. 이러한 분류 체계는 갱(Gang of Four)의 저서에서 처음 제시되었으며, 이후 널리 채택되었다.

첫 번째 범주는 생성 패턴이다. 이 패턴들은 객체 생성 메커니즘을 다루며, 객체를 생성하는 방법을 유연하게 만들어 시스템이 특정 클래스에 의존하지 않도록 한다. 대표적인 예로는 객체 생성을 서브클래스에 위임하는 팩토리 메서드 패턴, 복잡한 객체의 생성 과정을 단순화하는 빌더 패턴, 그리고 클래스의 인스턴스를 하나만 유지하는 싱글톤 패턴이 있다. 이 패턴들은 시스템이 어떻게 객체를 생성하고, 구성하고, 표현하는지와 관련된 문제를 해결한다.

두 번째 범주는 구조 패턴이다. 이 패턴들은 클래스나 객체들을 더 큰 구조로 조합하는 방법을 설명한다. 주로 상속을 활용하여 인터페이스나 구현을 구성하거나, 객체 합성을 통해 새로운 기능을 제공하는 데 중점을 둔다. 예를 들어, 호환되지 않는 인터페이스를 함께 작동하게 하는 어댑터 패턴, 객체에 동적으로 새로운 책임을 추가하는 데코레이터 패턴, 그리고 복잡한 서브시스템에 대한 간단한 인터페이스를 제공하는 퍼사드 패턴이 이에 속한다. 이들은 시스템의 구조적 유연성과 확장성을 높이는 데 기여한다.

세 번째 범주는 행동 패턴이다. 이 패턴들은 객체들 간의 알고리즘이나 책임 분배, 그리고 상호 통신 패턴에 초점을 맞춘다. 객체들이 어떻게 협력하고, 어떤 작업을 누가 수행할지, 그리고 통신 흐름을 어떻게 관리할지에 대한 해결책을 제시한다. 대표적인 패턴으로는 객체 사이의 일대다 의존 관계를 정의하여 한 객체의 상태 변화가 종속 객체들에게 자동으로 통지되도록 하는 옵저버 패턴, 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만드는 스트래티지 패턴, 그리고 요청을 객체로 캡슐화하여 매개변수화하고 지연 실행, 취소 등을 가능하게 하는 커맨드 패턴이 있다.

3.1. 생성 패턴

생성 패턴은 객체 생성 메커니즘을 다루며, 객체를 생성하는 방법과 시점을 유연하게 제어하는 데 초점을 맞춘다. 이 패턴들은 시스템이 특정 클래스에 의존하지 않고 객체를 생성할 수 있도록 하여, 객체 생성 과정을 캡슐화하고 추상화한다. 주로 객체 생성의 복잡성을 숨기거나, 생성 과정을 통제하여 시스템의 유연성과 재사용성을 높이는 목적으로 사용된다.

생성 패턴은 크게 클래스 생성 패턴과 객체 생성 패턴으로 나눌 수 있다. 클래스 생성 패턴은 상속을 이용하여 생성 과정을 하위 클래스에 위임하는 반면, 객체 생성 패턴은 객체 합성을 통해 책임을 다른 객체에 위임한다. 대표적인 생성 패턴의 예시는 다음과 같다.

패턴 이름

주요 목적

핵심 개념

싱글톤 패턴

클래스의 인스턴스를 하나만 생성하고 전역 접근점 제공

단일 인스턴스, 전역 상태

팩토리 메서드 패턴

객체 생성을 서브클래스에 위임하여 구체적인 클래스 의존성 제거

가상 생성자, 클래스 생성 패턴

추상 팩토리 패턴

관련된 객체 군을 생성하기 위한 인터페이스를 제공

객체 군 생성, 제품군

빌더 패턴

복잡한 객체의 생성 과정과 표현 방법을 분리

단계별 생성, Director와 Builder

프로토타입 패턴

기존 인스턴스를 복제하여 새로운 객체 생성

객체 복제, Clone 메서드

이러한 패턴들은 특정 문제 상황에 맞게 적용된다. 예를 들어, 싱글톤 패턴은 설정 관리자나 로거처럼 단 하나의 인스턴스만 필요한 경우에, 빌더 패턴은 많은 매개변수를 가진 복잡한 객체를 생성할 때 유용하다. 생성 패턴을 적절히 활용하면 코드의 결합도를 낮추고, 새로운 유형의 객체를 추가하기 쉬운 확장성 있는 설계를 가능하게 한다.

3.2. 구조 패턴

구조 패턴은 클래스나 객체의 구성을 통해 더 큰 구조를 형성하는 방법에 관한 패턴이다. 이 패턴들은 상속을 활용하여 인터페이스나 구현을 복합하는 데 중점을 둔다. 주로 시스템의 구조를 유연하고 효율적으로 설계하여 클래스와 객체 간의 관계를 단순화하거나 새로운 기능을 추가하기 쉽게 만드는 것이 목표이다.

대표적인 구조 패턴으로는 어댑터 패턴, 데코레이터 패턴, 퍼사드 패턴, 컴포지트 패턴, 프록시 패턴, 브리지 패턴, 플라이웨이트 패턴 등이 있다. 각 패턴은 특정한 구조적 문제를 해결한다. 예를 들어, 어댑터 패턴은 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동하도록 해주며, 데코레이터 패턴은 객체에 동적으로 새로운 책임을 추가한다.

이 패턴들은 주로 객체 간의 관계를 설계할 때 사용되며, 복잡한 시스템을 여러 계층으로 분리하거나 서브시스템을 더 쉽게 사용할 수 있게 만드는 데 유용하다. 구조 패턴을 적용하면 시스템의 부분들을 독립적으로 변경하거나 확장하기 쉬워져 전체적인 유지보수성이 향상된다.

패턴 이름

주요 목적

간단한 설명

어댑터 패턴

인터페이스 호환

서로 다른 인터페이스를 가진 클래스들이 협업할 수 있게 한다.

데코레이터 패턴

기능 동적 추가

객체에 새로운 행동을 런타임에 유연하게 첨가한다.

퍼사드 패턴

복잡성 감춤

복잡한 서브시스템에 대한 단순화된 통합 인터페이스를 제공한다.

컴포지트 패턴

부분-전체 계층

객체들을 트리 구조로 구성하여 개별 객체와 복합 객체를 동일하게 다룬다.

프록시 패턴

접근 제어

다른 객체에 대한 접근을 제어하거나 지연 초기화를 제공하는 대리 객체를 둔다.

3.3. 행동 패턴

행동 패턴은 객체나 클래스 간의 책임 할당, 알고리즘 분리, 그리고 상호작용 방식을 다루는 패턴이다. 이 패턴들은 객체의 행동을 유연하게 구성하거나 변경할 수 있도록 하며, 실행 중인 알고리즘이나 책임을 동적으로 교체하는 데 중점을 둔다. 주로 객체 간의 통신 흐름을 효율적으로 관리하고 복잡한 제어 로직을 단순화하는 데 목적이 있다.

행동 패턴의 주요 특징은 알고리즘을 캡슐화하거나 객체 간의 결합도를 낮추는 것이다. 예를 들어, 스트래티지 패턴은 유사한 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만든다. 이렇게 하면 알고리즘을 사용하는 클라이언트 코드는 변경 없이 다양한 전략을 사용할 수 있다. 반면, 옵저버 패턴은 한 객체의 상태 변화를 여러 관찰 객체에 자동으로 알리는 일대다 의존성을 정의하여 객체 간의 느슨한 결합을 유지한다.

다른 주요 행동 패턴으로는 다음과 같은 것들이 있다.

패턴 이름

주요 목적

간략한 설명

커맨드 패턴

요청을 객체로 캡슐화

요청 자체를 객체로 만들어 매개변수화, 지연 실행, 실행 취소 등의 기능을 지원한다.

이터레이터 패턴

집합 객체의 요소 순회

집합 객체의 내부 표현을 노출하지 않고 그 요소들을 순차적으로 접근하는 방법을 제공한다.

템플릿 메서드 패턴

알고리즘의 골격 정의

알고리즘의 골격을 상위 클래스에서 정의하고, 일부 단계를 하위 클래스에서 재정의하도록 한다.

책임 연쇄 패턴

요청 처리 체인 구성

요청을 처리할 수 있는 객체들을 체인으로 연결하고, 요청이 체인을 따라 차례로 전달되도록 한다.

미디에이터 패턴

객체 간의 복잡한 상호작용 캡슐화

객체들 간의 직접 통신을 제한하고 중재자 객체를 통해 협력하도록 함으로써 결합도를 낮춘다.

이러한 패턴들은 특정 행동 관련 문제를 해결하는 검증된 템플릿을 제공한다. 올바르게 적용하면 코드의 재사용성과 유지보수성을 크게 향상시킬 수 있다. 그러나 패턴의 본질적인 의도와 적용 컨텍스트를 정확히 이해하지 않고 사용하면 오히려 설계를 불필요하게 복잡하게 만들 수 있다는 점에 유의해야 한다.

4. 대표적인 디자인 패턴 예시

디자인 패턴은 특정한 설계 문제에 대한 검증된 해결책을 제시한다. 이 섹션에서는 가장 널리 알려지고 자주 사용되는 다섯 가지 패턴의 핵심 개념과 활용 예시를 설명한다.

싱글톤 패턴은 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 생성 패턴이다. 전역 변수를 사용하지 않고도 객체에 대한 전역적인 접근점을 제공하며, 데이터베이스 연결 풀, 로깅 객체, 설정 관리자 등 시스템 전체에서 단일 인스턴스가 필요한 경우에 주로 적용된다. 이 패턴은 생성자를 private으로 선언하고 정적 메서드를 통해 인스턴스에 접근하는 방식으로 구현된다.

팩토리 메서드 패턴 역시 생성 패턴에 속하며, 객체 생성을 서브클래스에 위임한다. 상위 클래스는 객체 생성 인터페이스만 정의하고, 실제 생성 로직은 하위 클래스가 결정한다. 이는 클라이언트 코드가 생성할 객체의 정확한 클래스를 알 필요 없이, 공통 인터페이스를 통해 객체를 사용할 수 있게 한다. 다양한 유형의 문서나 UI 컨트롤을 생성하는 프레임워크에서 흔히 발견된다.

패턴명

분류

주요 목적

간단한 예시

싱글톤 패턴

생성

단일 인스턴스 보장

애플리케이션 설정 관리자

팩토리 메서드 패턴

생성

구체적 클래스 생성 위임

문서 편집기의 문서 생성기

옵저버 패턴

행동

상태 변화 알림

GUI 이벤트 처리, Pub/Sub 시스템

어댑터 패턴

구조

호환되지 않는 인터페이스 연결

레거시 코드를 새 시스템에 통합

스트래티지 패턴

행동

알고리즘 교체 가능

다양한 정렬 또는 결제 방식 구현

옵저버 패턴은 객체 사이의 일대다 의존 관계를 정의하는 행동 패턴이다. 한 객체(주제)의 상태가 변경되면 그 객체에 의존하는 모든 객체(옵저버)들에게 자동으로 알림이 전달되고 갱신된다. 이 패턴은 이벤트 드리븐 시스템, 사용자 인터페이스 컴포넌트, 모델-뷰-컨트롤러 아키텍처에서 널리 사용된다.

어댑터 패턴은 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동할 수 있도록 하는 구조 패턴이다. 기존 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환하는 역할을 한다. 이는 마치 전원 플러그의 돼지코 변환기와 유사하다. 레거시 시스템을 새로운 시스템과 연동하거나, 서드파티 라이브러리의 인터페이스를 프로젝트의 인터페이스에 맞출 때 유용하다.

스트래티지 패턴은 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만드는 행동 패턴이다. 클라이언트는 독립적으로 알고리즘을 선택하고 사용할 수 있다. 이 패턴은 if-else나 switch-case 문으로 구현된 여러 알고리즘을 분리하여 관리할 때 효과적이다. 예를 들어, 네비게이션 시스템에서 다양한 경로 탐색 알고리즘(최단 거리, 최소 시간, 고속도로 우선)을 상황에 따라 선택하여 적용하는 데 활용될 수 있다.

4.1. 싱글톤 패턴

싱글톤 패턴은 생성 패턴에 속하며, 특정 클래스의 인스턴스가 오직 하나만 생성되도록 보장하고, 이 인스턴스에 대한 전역적인 접근점을 제공하는 패턴이다. 주로 데이터베이스 연결 풀, 로깅 객체, 설정 관리자, 캐시 등 애플리케이션 전반에서 단일 인스턴스만 존재해야 하는 공유 자원을 관리할 때 사용된다.

이 패턴의 핵심은 생성자를 외부에서 호출할 수 없도록 private으로 선언하고, 클래스 자신이 유일한 인스턴스를 생성하여 저장하는 정적 멤버 변수를 두는 것이다. 외부에서는 이 정적 멤버를 반환하는 정적 메서드(예: getInstance())를 통해서만 인스턴스에 접근할 수 있다. 이는 게으른 초기화 방식으로 처음 요청이 있을 때 인스턴스를 생성하거나, 클래스 로딩 시점에 미리 생성하는 방식으로 구현된다.

싱글톤 패턴의 주요 구현 방식과 고려사항은 다음과 같다.

구현 방식

설명

주의사항

게으른 초기화

getInstance() 메서드가 처음 호출될 때 인스턴스를 생성한다.

멀티스레드 환경에서 동기화 처리가 필요하다.

이른 초기화

클래스 로딩 시점에 정적 변수로 인스턴스를 미리 생성한다.

사용 여부와 관계없이 인스턴스가 생성되어 리소스를 차지할 수 있다.

더블 체크 락킹

게으른 초기화 방식을 멀티스레드 환경에서 효율적으로 동기화한다.

구현이 복잡해질 수 있다.

이 패턴은 전역 상태를 만들 수 있어 단위 테스트를 어렵게 할 수 있다는 단점이 있다. 또한, 의존성 주입 원칙을 위반할 가능성이 있어 과도한 사용은 지양해야 한다.

4.2. 팩토리 메서드 패턴

팩토리 메서드 패턴은 객체 생성을 처리하는 생성 패턴 중 하나이다. 이 패턴은 상위 클래스에서 객체를 생성하는 인터페이스를 정의하되, 하위 클래스가 생성할 객체의 구체적인 타입을 결정하도록 한다. 이를 통해 객체 생성 코드를 하위 클래스로 분리하여, 상위 클래스의 코드가 특정 구현 클래스에 의존하지 않게 만든다.

패턴의 핵심 구조는 Creator와 Product라는 두 가지 추상 요소로 구성된다. Creator는 팩토리 메서드를 선언하는 추상 클래스 또는 인터페이스이며, 이 메서드는 Product 타입의 객체를 반환한다. ConcreteCreator 클래스들은 이 팩토리 메서드를 오버라이드하여 특정 ConcreteProduct 인스턴스를 생성하고 반환한다. 결과적으로 클라이언트 코드는 Creator 추상 타입을 통해 객체를 생성하게 되며, 실제로 어떤 구체적인 제품이 만들어지는지는 런타임에 결정된다.

이 패턴의 주요 이점은 결합도를 낮추는 데 있다. 시스템에 새로운 제품 타입을 추가해야 할 때, 기존의 클라이언트 코드를 수정하지 않고 새로운 ConcreteCreator 클래스를 도입하기만 하면 된다. 이는 개방-폐쇄 원칙을 잘 반영한 사례이다. 또한, 생성 로직이 한 곳에 집중되어 코드의 유지보수성이 향상된다.

팩토리 메서드 패턴은 의존성 주입 프레임워크나 다양한 GUI 라이브러리에서 위젯을 생성할 때, 그리고 객체 풀을 관리할 때 흔히 활용된다. 단순한 객체 생성이 아니라 생성 과정에 복잡한 로직이 포함되거나, 생성될 객체의 타입이 실행 시점에 동적으로 결정되어야 하는 상황에서 특히 유용하다.

4.3. 옵저버 패턴

옵저버 패턴은 객체 사이의 일대다 의존 관계를 정의하여, 한 객체(주제 또는 발행자)의 상태가 변경될 때 그 객체에 의존하는 모든 객체(옵저버 또는 구독자)에게 자동으로 알림이 가고 갱신될 수 있도록 하는 행동 패턴이다. 이 패턴은 주로 분산된 시스템 간의 이벤트 처리를 효과적으로 관리하기 위해 사용된다.

패턴의 핵심 구조는 주제 인터페이스와 옵저버 인터페이스로 구성된다. 주제는 옵저버들을 등록, 제거, 알림하는 메서드를 제공한다. 옵저버는 상태 갱신을 위한 메서드(예: update())를 정의한다. 구체적인 주제 객체는 상태를 저장하고, 상태가 변경되면 등록된 모든 옵저버 객체의 갱신 메서드를 호출한다. 구체적인 옵저버 객체는 주제에 자신을 등록하고, 주제로부터의 알림을 받아 자신의 상태를 적절히 동기화한다.

이 패턴의 주요 적용 사례로는 GUI 프로그래밍에서의 이벤트 처리 시스템, 모델-뷰-컨트롤러 아키텍처에서 모델과 뷰의 분리, 그리고 발행-구독 메시징 시스템 등이 있다. 예를 들어, 사용자 인터페이스에서 데이터 모델이 변경되면 이를 표시하는 여러 뷰들이 자동으로 새로고침되는 구조를 구현할 때 유용하다.

옵저버 패턴의 장점은 주제와 옵저버 간의 느슨한 결합을 유지한다는 점이다. 주제는 옵저버들의 구체적인 클래스를 알 필요 없이 인터페이스만을 통해 통신한다. 이는 새로운 유형의 옵저버를 쉽게 추가할 수 있게 하여 시스템의 확장성을 높인다. 그러나 많은 수의 옵저버가 등록되어 있을 경우 알림 순서가 중요해지거나, 성능에 영향을 미칠 수 있으며, 옵저버가 제대로 해제되지 않으면 메모리 누수가 발생할 수 있는 주의점도 존재한다.

4.4. 어댑터 패턴

어댑터 패턴은 서로 다른 인터페이스를 가진 클래스들이 함께 동작할 수 있도록 중간에서 변환 역할을 하는 구조 패턴이다. 이 패턴은 호환되지 않는 인터페이스를 가진 기존 클래스를 다른 클래스에서 사용해야 할 때, 기존 클래스를 수정하지 않고도 연결할 수 있게 해준다. 전기 콘센트의 변환 플러그와 유사한 역할을 수행한다고 볼 수 있다.

어댑터 패턴은 주로 두 가지 방식으로 구현된다. 첫 번째는 클래스에 의한 어댑터 패턴으로, 어댑터 클래스가 타깃 인터페이스를 상속받고, 동시에 어댑티 클래스를 포함하는 방식이다. 두 번째는 객체에 의한 어댑터 패턴으로, 어댑터 클래스가 타깃 인터페이스를 구현하고 내부에 어댑티 객체를 합성하여 사용하는 방식이다. 후자가 더 유연한 구조를 가지는 경우가 많다.

이 패턴의 주요 구성 요소는 다음과 같다.

구성 요소

역할

타깃

클라이언트가 기대하는 인터페이스를 정의한다.

어댑티

기존에 존재하지만 호환되지 않는 인터페이스를 가진 클래스이다.

어댑터

어댑티의 인터페이스를 타깃 인터페이스로 변환하는 클래스이다.

클라이언트

타깃 인터페이스를 통해 객체와 상호작용하는 프로그램 부분이다.

어댑터 패턴은 레거시 시스템을 새로운 시스템과 통합해야 하거나, 서로 다른 벤더가 제공한 라이브러리를 함께 사용해야 할 때 매우 유용하다. 기존 코드를 재사용할 수 있게 하면서도, 클라이언트 코드는 일관된 인터페이스를 통해 작업을 수행할 수 있다. 그러나 너무 많은 어댑터가 생성되면 시스템의 전체 구조가 복잡해질 수 있다는 점에 유의해야 한다.

4.5. 스트래티지 패턴

스트래티지 패턴은 행동 패턴에 속하는 디자인 패턴으로, 특정 작업을 수행하는 여러 알고리즘을 정의하고 각 알고리즘을 별도의 클래스로 캡슐화하여 상호 교환 가능하게 만드는 패턴이다. 이 패턴의 핵심은 알고리즘을 사용하는 클라이언트 코드와 알고리즘의 구현을 분리하는 것이다. 클라이언트는 구체적인 알고리즘 구현이 아닌, 공통의 인터페이스에만 의존하게 된다.

주요 구성 요소는 다음과 같다. 첫째, 알고리즘의 공통 인터페이스를 정의하는 전략 인터페이스가 있다. 둘째, 이 인터페이스를 구현하는 구체적인 알고리즘 클래스들이다. 셋째, 전략 인터페이스를 참조하여 알고리즘을 사용하는 컨텍스트 클래스이다. 컨텍스트는 전략 객체를 구성(주입)받아 이를 통해 작업을 위임한다. 이로 인해 알고리즘을 런타임에 동적으로 변경할 수 있다.

이 패턴의 가장 큰 장점은 개방-폐쇄 원칙을 잘 준수한다는 점이다. 새로운 알고리즘을 추가하려면 새로운 전략 클래스를 만들기만 하면 되며, 기존의 컨텍스트 코드를 수정할 필요가 없다. 또한 조건문(if-else 또는 switch 문)을 제거하여 코드를 간결하게 하고, 각 알고리즘을 독립적으로 테스트하기 용이하다.

일반적인 적용 예시는 다음과 같다.

적용 분야

예시

정렬 알고리즘

컨텍스트가 데이터 목록을 정렬할 때, 퀵 정렬, 병합 정렬, 버블 정렬 등의 전략을 상황에 맞게 교체

결제 수단 처리

결제를 처리하는 컨텍스트가 신용카드, 페이팔, 암호화폐 등 다양한 결제 전략을 지원

파일 압축

파일을 압축하는 프로그램이 ZIP, RAR, 7z 등 다른 압축 알고리즘을 전략으로 사용

내비게이션 경로 탐색

경로 찾기 컨텍스트가 최단 경로, 최소 시간, 고속도로 우선 등 다른 탐색 전략을 제공

단, 적용할 알고리즘이 단 하나뿐이거나 거의 변하지 않는 경우에는 불필요한 복잡성을 초래할 수 있다. 또한 클라이언트가 적절한 전략 객체를 생성하고 컨텍스트에 주입해야 하는 책임이 생긴다.

5. 디자인 패턴의 장점과 한계

디자인 패턴을 올바르게 적용하면 여러 가지 이점을 얻을 수 있다. 가장 큰 장점은 검증된 해결책을 재사용함으로써 설계 시간을 단축하고 코드의 품질을 높일 수 있다는 점이다. 개발자들은 특정 문제에 대한 최적의 구조를 처음부터 고민하지 않고, 패턴에 정의된 템플릿을 따라 효율적으로 구현할 수 있다. 또한, 패턴은 공통된 용어를 제공하여 개발자 간의 의사소통을 원활하게 한다. "이 부분은 옵저버 패턴으로 구현하자"와 같은 대화는 복잡한 설계 개념을 간결하게 전달한다. 잘 알려진 패턴을 사용하면 코드의 가독성과 유지보수성이 향상되며, 새로운 팀원도 시스템 구조를 더 쉽게 이해할 수 있다.

그러나 디자인 패턴에는 몇 가지 명백한 한계와 위험성이 존재한다. 가장 흔한 문제는 패턴의 과도한 적용, 즉 오버엔지니어링이다. 간단한 문제에 복잡한 패턴을 도입하면 불필요한 추상화 계층이 생겨 코드가 장황해지고 성능에 부정적인 영향을 미칠 수 있다. 패턴은 도구이지 목적이 아니라는 점을 인식해야 한다. 또한, 패턴은 특정 프로그래밍 패러다임에 종속되는 경우가 많다. 예를 들어, 대부분의 고전적인 GoF 디자인 패턴은 객체지향 프로그래밍을 전제로 하여, 함수형 프로그래밍 언어나 패러다임에서는 다른 방식으로 접근해야 할 수 있다.

디자인 패턴의 또 다른 한계는 올바르게 적용하기 위한 학습 곡선이 필요하다는 점이다. 패턴을 표면적으로만 이해하고 맥락 없이 적용하면, 오히려 설계를 더 복잡하고 유연하지 못하게 만들 위험이 있다. 각 패턴은 해결하려는 특정 문제와 적용 조건을 명확히 이해한 후에 사용해야 한다. 결국, 디자인 패턴의 진정한 가치는 반복적으로 발생하는 설계 문제에 대한 지식과 경험을 체계화한 데 있으며, 이러한 지식을 맹목적으로 따르기보다는 현재 직면한 문제의 맥락에 비판적으로 적용할 때 빛을 발한다.

6. 디자인 패턴 적용 원칙

디자인 패턴을 효과적으로 적용하기 위해서는 몇 가지 핵심 원칙을 준수해야 한다. 가장 중요한 원칙은 문제 상황에 맞는 패턴 선택이다. 모든 패턴은 특정 문제 상황과 제약 조건을 해결하기 위해 고안되었다. 따라서 설계상의 문제가 명확히 식별되지 않은 상태에서 패턴을 적용하는 것은 적절하지 않다. 예를 들어, 객체 생성 과정이 복잡한 경우 팩토리 메서드 패턴이나 추상 팩토리 패턴을 고려할 수 있지만, 단순한 객체 생성에는 불필요한 복잡성을 더할 뿐이다. 패턴을 선택할 때는 항상 해결하려는 문제의 본질과 패턴이 제공하는 해법의 적합성을 평가해야 한다.

또 다른 중요한 원칙은 과도한 적용의 위험성을 인지하는 것이다. 디자인 패턴은 만병통치약이 아니다. 필요 이상으로 많은 패턴을 도입하면 코드베이스가 불필요하게 복잡해지고, 유지보수가 어려워지며, 성능에 부정적인 영향을 미칠 수 있다. 이 현상을 오버엔지니어링이라고 부른다. 패턴 적용은 항상 단순함과 복잡성 사이의 균형을 고려해야 한다. 가장 간단하고 명확한 해결책이 존재한다면, 그것이 패턴을 사용한 해결책보다 종종 더 나은 선택이다.

패턴을 적용할 때는 해당 패턴의 의도와 구조를 정확히 이해하고, 프로젝트의 아키텍처와 코딩 컨벤션에 자연스럽게 통합시켜야 한다. 패턴을 적용한 결과 코드의 가독성과 유연성이 향상되어야 하며, 새로운 개발자가 코드를 이해하는 데 장벽이 되어서는 안 된다. 또한, 선택한 패턴이 시스템의 다른 부분과 어떻게 상호작용할지, 미래의 변경 사항을 수용할 수 있을지에 대한 전망도 고려해야 한다. 결국 디자인 패턴은 목적이 아니라, 더 나은 소프트웨어 설계를 달성하기 위한 수단이다.

6.1. 문제 상황에 맞는 패턴 선택

적절한 디자인 패턴을 선택하는 것은 패턴의 이점을 최대화하고 부작용을 최소화하는 핵심 단계이다. 올바른 선택은 먼저 해결해야 할 구체적인 문제 상황을 명확히 정의하는 데서 시작한다. 예를 들어, 객체 생성 과정이 복잡하거나 유연성이 필요한 경우 생성 패턴을, 기존 클래스들의 인터페이스를 호환시켜야 하는 경우 구조 패턴을, 알고리즘이나 객체 간 책임 분배, 통신 방식을 정의해야 하는 경우 행동 패턴을 후보로 고려한다.

패턴 선택 시에는 해당 패턴이 해결하려는 의도와 적용 결과를 충분히 이해해야 한다. 각 패턴은 특정 문제 상황에 대한 검증된 해결책을 제시하지만, 그 해결책이 항상 현재 직면한 문제의 맥락에 완벽히 부합하는 것은 아니다. 따라서 패턴의 구조뿐만 아니라 패턴 간의 협력 관계와 트레이드오프도 고려해야 한다. 예를 들어, 팩토리 메서드 패턴은 템플릿 메서드 패턴과 함께 사용되기도 하며, 옵저버 패턴은 복잡한 의존성을 관리하는 대신 성능 저하나 메모리 누수의 위험을 초래할 수 있다.

최종 결정은 시스템의 전체적인 설계 목표와 제약 조건에 기반해야 한다. 아래 표는 몇 가지 일반적인 문제 상황과 고려할 수 있는 대표적인 패턴 후보를 보여준다.

문제 상황

고려할 패턴 후보

주요 고려 사항

전역적으로 하나의 인스턴스만 필요함

싱글톤 패턴

테스트 용이성, 동시성 제어

관련 객체들을 구체적인 클래스를 지정하지 않고 생성해야 함

추상 팩토리 패턴, 팩토리 메서드 패턴

생성품군의 확장성, 코드 복잡도

기존 클래스의 인터페이스를 다른 인터페이스로 변환해야 함

어댑터 패턴

기능 추가(데코레이터 패턴) vs 인터페이스 변환

알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 해야 함

스트래티지 패턴

알고리즘의 빈번한 변경, 조건문의 제거

객체의 상태 변화를 다른 객체들에게 알려야 함

옵저버 패턴

주체와 관찰자의 결합도, 효율적인 알림 메커니즘

결국, 패턴은 목적이 아니라 수단이다. 문제의 본질을 정확히 파악한 후, 패턴 카탈로그를 '해결책 사전'이 아니라 '설계 아이디어의 보고'로 참조하여 가장 적합한 설계를 도출하는 것이 중요하다.

6.2. 과도한 적용의 위험성

디자인 패턴은 반복적으로 발생하는 설계 문제에 대한 검증된 해결책을 제공하지만, 모든 상황에 무조건 적용해야 하는 만능 도구는 아니다. 패턴의 과도한 적용은 오히려 설계를 불필요하게 복잡하게 만들고, 코드의 가독성과 유지보수성을 떨어뜨릴 수 있다. 간단한 문제를 해결하는 데 여러 개의 패턴을 중첩해서 사용하면, 설계 의도를 파악하기 어려워지고 런타임 성능에도 부정적인 영향을 미칠 수 있다.

패턴 적용의 가장 큰 위험성은 문제의 본질을 이해하지 않고 패턴 자체를 목적으로 삼는 경우다. 이는 '솔루션을 찾을 문제를 만들기'에 해당한다. 예를 들어, 단순한 조건문으로 처리할 수 있는 변형을 위해 스트래티지 패턴을 도입하거나, 인스턴스 생성이 한 번만 일어나는 것이 분명한 상황에서 싱글톤 패턴을 적용하는 것은 설계 과잉의 전형적인 예시다. 패턴은 문제에서 출발해야 하며, 해결책이 아닌 문제가 먼저 존재해야 한다.

과도한 패턴 적용은 다음과 같은 구체적인 문제를 초래한다.

위험 요소

설명

불필요한 복잡성

간단해야 할 코드가 여러 클래스와 추상화 계층으로 인해 이해하기 어려워진다.

성능 오버헤드

불필요한 간접 참조와 객체 생성으로 인해 실행 시간이 늘어날 수 있다.

유연성의 역설

미래의 변경을 대비한 과도한 유연성 설계는 현재의 코드를 더 취약하고 변경하기 어렵게 만든다.

학습 곡선

새로운 개발자가 복잡한 패턴 구조를 이해하는 데 시간이 많이 소요된다.

따라서 패턴을 적용할 때는 YAGNI(You Ain't Gonna Need It) 원칙과 KISS(Keep It Simple, Stupid) 원칙을 상기하는 것이 중요하다. 현재 명확히 요구되는 기능과 예상되는 변경만을 고려하여 최소한의 설계를 선택해야 한다. 디자인 패턴은 설계를 개선하는 수단이지, 그 자체가 목표가 되어서는 안 된다.

7. 다른 프로그래밍 패러다임과의 관계

디자인 패턴은 주로 객체지향 프로그래밍의 맥락에서 발전하고 논의되었다. 대부분의 고전적인 GoF 디자인 패턴은 클래스와 객체 간의 관계를 정의하여 유연하고 재사용 가능한 객체지향 설계를 달성하는 것을 목표로 한다. 예를 들어, 상속, 캡슐화, 다형성 같은 객체지향 원칙은 팩토리 메서드 패턴이나 스트래티지 패턴과 같은 패턴의 구현 기반이 된다. 따라서 디자인 패턴을 효과적으로 이해하고 적용하기 위해서는 객체지향의 기본 원리에 대한 지식이 필수적이다.

반면, 함수형 프로그래밍 패러다임은 상태 변경과 부작용을 최소화하고 순수 함수의 조합을 강조한다. 이 패러다임에서는 일부 전통적인 디자인 패턴이 필요 없거나 다른 형태로 구현된다. 예를 들어, 옵저버 패턴은 함수형 언어에서 이벤트 스트림이나 고차 함수를 이용한 구독 모델로 대체될 수 있다. 스트래티지 패턴은 단순히 함수를 인자로 전달하는 방식으로 구현되어 더 간결해진다. 함수형 프로그래밍은 불변성과 선언형 프로그래밍을 통해 새로운 수준의 모듈성과 테스트 용이성을 제공하며, 이는 일부 디자인 패턴이 해결하려 했던 문제를 근본적으로 다른 방식으로 접근하게 한다.

두 패러다임의 관계는 상호 배타적이지 않으며, 현대 프로그래밍에서는 혼합되어 사용되는 경우가 많다. 많은 객체지향 언어가 람다 표현식과 스트림 API 같은 함수형 기능을 도입했다. 이는 디자인 패턴의 구현을 더 간소화하고 표현력을 높인다. 결과적으로, 디자인 패턴은 특정 패러다임에 종속된 rigid한 규칙이 아니라, 다양한 프로그래밍 스타일 하에서 공통적인 설계 문제에 대한 유용한 어휘와 솔루션 템플릿으로 진화하고 있다.

7.1. 객체지향 프로그래밍

객체지향 프로그래밍은 디자인 패턴의 등장과 발전에 가장 직접적인 영향을 미친 프로그래밍 패러다임이다. 디자인 패턴의 대부분은 객체지향의 핵심 원리인 캡슐화, 상속, 다형성을 효과적으로 활용하여 설계 문제를 해결하는 방법을 제시한다. 예를 들어, 스트래티지 패턴은 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하도록 만드는데, 이는 다형성을 이용한 전형적인 해결책이다.

디자인 패턴은 객체지향 설계에서 반복적으로 발생하는 문제에 대한 검증된 해결 방안을 문서화한 것이다. 객체지향 프로그래밍의 목표 중 하나는 변경에 유연하고 재사용성이 높은 소프트웨어를 만드는 것이며, 디자인 패턴은 이러한 목표를 달성하는 구체적인 설계 기법을 제공한다. 팩토리 메서드 패턴은 객체 생성 로직을 캡슐화하고, 어댑터 패턴은 호환되지 않는 인터페이스를 함께 작동하게 함으로써 시스템의 결합도를 낮춘다.

다음 표는 주요 객체지향 원리와 이를 활용하는 대표적인 디자인 패턴의 관계를 보여준다.

객체지향 원리

설명

관련 디자인 패턴 예시

캡슐화

데이터와 메서드를 하나의 단위로 묶고, 내부 구현을 숨기는 것

팩토리 메서드 패턴, 싱글톤 패턴

상속

기존 클래스의 속성과 기능을 새로운 클래스가 물려받는 것

템플릿 메서드 패턴, 데코레이터 패턴

다형성

하나의 인터페이스나 부모 클래스를 통해 다양한 형태의 객체를 동일하게 다루는 것

스트래티지 패턴, 옵저버 패턴, 커맨드 패턴

따라서 디자인 패턴을 효과적으로 이해하고 적용하기 위해서는 객체지향 프로그래밍의 기본 원리에 대한 깊은 이해가 선행되어야 한다. 패턴은 객체지향 설계의 어려움을 해소하고, 보다 우아하고 견고한 코드 구조를 만드는 데 기여한다.

7.2. 함수형 프로그래밍

함수형 프로그래밍은 상태 변경과 가변 데이터를 피하고, 순수 함수의 조합과 고차 함수의 적용을 중심으로 프로그램을 구성하는 패러다임이다. 이 패러다임은 디자인 패턴과의 관계에서 두 가지 주요 관점을 보인다. 첫째, 전통적인 객체지향 디자인 패턴 중 상당수는 함수형 프로그래밍의 기본 개념으로 더 간결하게 표현되거나 대체될 수 있다. 예를 들어, 스트래티지 패턴은 고차 함수에 함수를 인자로 전달하는 방식으로, 옵저버 패턴은 스트림과 같은 데이터 흐름 추상화로 구현될 수 있다. 둘째, 함수형 프로그래밍은 모나드, 펑터, 리프팅과 같은 새로운 수준의 추상화와 패턴을 발전시켰다.

이러한 패턴들은 부수 효과를 제어하고 합성을 강조하는 함수형 프로그래밍의 핵심 문제를 해결한다. 결과적으로, 함수형 프로그래밍 환경에서는 일부 고전적인 디자인 패턴의 필요성이 줄어들거나 형태가 변형되는 경향이 있다. 반면, 두 패러다임을 혼합하는 현대적인 언어(예: 스칼라, 코틀린, 스위프트)에서는 객체지향 구조와 함수형 패턴이 공존하며, 상황에 맞는 최적의 설계를 위해 함께 활용된다.

8. 관련 문서

  • Wikipedia - 디자인 패턴 (컴퓨터 과학)

  • Wikipedia - Design pattern

  • 나무위키 - 디자인 패턴

  • Refactoring.Guru - 디자인 패턴 카탈로그

  • 한국저작권위원회 - 디자인보호법의 보호대상

  • Oracle - Java SE 디자인 패턴

  • Microsoft Learn - .NET의 일반적인 디자인 패턴

  • Google Scholar - Design Patterns: Elements of Reusable Object-Oriented Software

리비전 정보

버전r1
수정일2026.02.14 21:42
편집자unisquads
편집 요약AI 자동 생성