1. 디자인 패턴의 개요
1.1. 디자인 패턴이란?
- 디자인 패턴 (Design Pattern)
- 소프트웨어 모듈의 세분화된 역할이나 모듈 간의 인터페이스 등을 설계할 때 참고할 수 있는 전형적인 해결 방식, 예제, 재사용 가능한 샘플 코드
1.2. 디자인 패턴의 주요 특징
-
“바퀴를 다시 발명하지 마라 (Don’t reinvent the wheel)”
- 개발 중 문제가 발생했을 때 처음부터 새로운 해결책을 만들어내느라 시간을 낭비하지 말고,
- 이미 검증된 디자인 패턴을 참고하여 적용하는 것이 훨씬 효율적이라는 의미이다.
-
디자인 패턴 안에는 문제와 배경, 실제 적용 사례, 재사용 가능한 샘플 코드 등이 모두 담겨 있다.
-
상황에 맞게 하나의 패턴을 변형하거나 요구사항을 반영해 다른 형태의 패턴으로 유연하게 변화시킬 수 있다.
1.3. GoF (Gang of Four)의 디자인 패턴
-
탄생
- 1995년 에릭 감마, 리차드 헬름, 랄프 존슨, 존 블리시디스 등 4명의 학자(GoF)가
- 수많은 디자인 패턴 중 가장 보편적으로 적용할 수 있는 것들을 모아 처음으로 체계화했다.
-
현재까지도 소프트웨어 공학 및 현업에서 가장 많이 사용되는 표준적인 디자인 패턴이다.
3. 디자인 패턴 사용의 장단점
3.1. 장점
-
구조 파악 용이
- 범용적인 코딩 스타일을 사용하므로 시스템의 구조를 쉽게 파악할 수 있다.
-
생산성 향상
- 객체지향 설계 및 구현의 생산성을 높이는 데 매우 적합하다.
-
시간 및 비용 절감
- 이미 검증된 패턴 구조를 재사용하기 때문에 개발에 드는 시간과 비용을 절약할 수 있다.
-
원활한 의사소통
- 개발자 간 공통된 패턴(언어)을 사용하므로 원활한 의사소통이 가능하다.
-
유연한 대처
- 요구사항이나 설계 변경 요청이 있을 때 유연하게 대처하고 수정할 수 있다.
3.2. 단점
-
초기 투자 비용 부담
- 요구사항을 직관적으로 바로 구현하는 것이 아니라 패턴에 맞춰 설계하고 구현해야 하므로 초기에 시간과 노력(투자 비용)이 많이 들 수 있다.
-
객체지향에 국한됨
- 객체지향을 기반으로 한 설계와 구현을 다루기 때문에, 다른 프로그래밍 패러다임 기반의 애플리케이션 개발에는 적합하지 않다.
4. 생성 패턴
- 생성 패턴
- 객체의 생성과 참조 과정을 캡슐화하여, 객체가 생성되거나 변경되더라도 프로그램 구조에 영향을 크게 받지 않도록 유연성을 더해주는 패턴
4.1. 싱글톤
-
개념
- 특정 클래스 내에서 인스턴스가 오직 하나뿐임을 보장하는 패턴
-
특징
- 생성된 하나의 객체를 어디서든 참조할 수 있다.
- 여러 프로세스가 동시에 참조할 수 없다.
- 불필요한 메모리 낭비를 최소화할 수 있다.
4.2. 팩토리 메서드
-
개념
- 상위 클래스에서는 인터페이스만 정의하고,
- 실제 객체의 생성은 하위(서브) 클래스에서 담당하도록 분리하여 캡슐화한 패턴
-
특징
- 하위 클래스에서 어떤 객체를 생성할지 결정하므로,
- ‘가상 생성자(Virtual Constructor)’ 패턴이라고도 부른다.
4.3. 추상 팩토리
-
개념
- 구체적인 클래스(하위 클래스)에 의존하지 않고,
- 인터페이스를 통해 서로 연관되거나 의존하는 객체들을 그룹으로 묶어 추상적으로 표현하는 패턴
-
특징
- 연관된 서브 클래스 그룹을 묶어서 한 번에 교체하는 것이 가능
4.4. 빌더
-
개념
- 작게 분리된 인스턴스들을 건축하듯 차곡차곡 조합하여 하나의 객체를 생성하는 패턴
-
특징
- 객체의 생성 과정과 표현 방법을 분리한다.
- 따라서 동일한 생성 과정(예: 인사말 생성)을 거치더라도 서로 다른 결과(영어, 중국어, 일어 등)를 만들어 낼 수 있다.
4.5. 프로토타입
-
개념
- 원본 객체를 복제(Copy)하는 방식으로 새로운 객체를 생성하는 패턴
-
특징
- 일반적인 방법으로 객체를 생성할 때 비용(시간, 리소스)이 매우 큰 경우에 주로 사용
5. 구조 패턴
- 구조 패턴
- 클래스나 객체들을 조합하여 더 큰 구조로 만들고,
- 이를 통해 복잡한 시스템을 개발하기 쉽도록 돕는 패턴
5.1. 어댑터
-
개념
- 호환성이 없는 클래스들의 인터페이스를 다른 클래스가 이용할 수 있도록 변환해 주는 패턴
-
특징
- 기존의 클래스를 수정하지 않고 인터페이스를 맞춰준다.
- (비유: 스마트폰 충전기 젠더)
5.2. 데코레이터
-
개념
- 객체 간의 결합을 통해 능동적으로 기능을 확장할 수 있는 패턴
-
특징
- 임의의 객체에 부가적인 기능을 추가하기 위해,
- 기본 객체에 다른 객체를 덧붙이는(포장하는) 방식으로 구현
5.3. 브리지
-
개념
- 구현부에서 추상층(기능)을 분리하여,
- 서로가 독립적으로 확장할 수 있도록 구성한 패턴
-
특징
- 기능과 구현을 서로 다른 별도의 클래스로 구현
5.4. 컴포지트
-
개념
- 단일 객체와 복합 객체를 구분 없이 동일하게 다루고자 할 때 사용하는 패턴
-
특징
- 폴더(디렉터리) 안에 또 폴더가 들어갈 수 있는 것처럼,
- 객체들을 트리(Tree) 구조로 구성
5.5. 퍼싸드
-
개념
- 복잡한 하위(서브) 클래스들을 피하고,
- 더 상위에 통합 인터페이스를 구성하여 서브 클래스들의 기능을 간편하게 사용할 수 있도록 하는 패턴
-
특징
- 서브 클래스들 사이의 통합 인터페이스를 제공하는 Wrapper(래퍼) 객체가 필요
5.6. 플라이웨이트
-
개념
- 인스턴스가 필요할 때마다 매번 새로 생성하는 것이 아니라,
- 가능한 한 공유해서 사용함으로써 메모리를 절약하는 패턴이다.
-
특징
- 다수의 유사한 객체를 생성하거나 조작할 때 유용하며, 메모리 절약이 핵심 목적
5.7. 프록시
- 개념
- 접근이 어려운 객체(네트워크 연결, 대용량 객체 등)와 연결하려 할 때,
- 해당 객체에 바로 접근하지 않고 대신 인터페이스 역할을 수행해 주는 패턴
6. 행동 패턴
- 행동 패턴
- 클래스나 객체들이 서로 상호작용하는 방법이나 책임을 분배하는 방법을 정의하는 패턴
- 하나의 객체가 혼자 수행할 수 없는 작업을 여러 객체로 분배하여 객체 간의 결합도를 최소화하는 것이 목적
6.1. 11가지 행위 패턴 종류 및 특징
-
반복자
- 개념: 자료 구조(배열 등)의 내부 표현을 노출하지 않고, 그 안의 객체들에 순차적으로 접근할 수 있게 해주는 패턴
-
상태
- 개념: 객체의 내부 상태에 따라 동일한 동작을 다르게 처리해야 할 때 사용하는 패턴
- (비유: 엄마의 기분 상태에 따라 용돈을 더 받거나 못 받는 상황)
-
옵서버
- 개념: 한 객체의 상태가 변화하면, 그 객체에 상속(구독)되어 있는 다른 객체들에게 변화된 상태를 자동으로 전달하는 패턴
- (비유: 단톡방에서 내가 메시지를 읽으면 다른 사람 화면의 안 읽음 숫자가 줄어드는 기능)
-
책임 연쇄 (Chain of Responsibility)
- 개념: 요청을 처리할 수 있는 객체들을 고리(Chain)로 묶어, 한 객체가 처리하지 못하면 다음 객체로 책임을 넘기는 패턴
- (비유: 책임을 다음 사람에게 넘기는 의리 게임)
-
커맨드 (Command)
- 개념: 요청을 객체의 형태로 캡슐화하여 재이용하거나 취소할 수 있도록 하는 패턴
- 요청에 필요한 정보를 저장하거나 로그에 남길 수 있다.
-
인터프리터 (Interpreter)
- 개념: 특정 언어의 문법 표현을 정의하는 패턴
- (비유: 맞춤법 검사기, SQL 구문 분석 등)
-
중재자 (Mediator)
- 개념: 수많은 객체들 간의 복잡한 상호작용을 캡슐화하여, 객체들이 직접 통신하지 않고 중재자를 통해 통신하게 함으로써 결합도를 낮추는 패턴
- (비유: 친구들 싸움을 말리고 의견을 조율하는 중재자)
-
메멘토 (Memento)
- 개념: 특정 시점의 객체 내부 상태를 객체화하여 저장해 두었다가, 필요할 때 그 상태로 되돌릴 수 있게 하는 패턴
- (비유: 실행 취소(Ctrl+Z), 되돌리기 기능)
-
전략 (Strategy)
- 개념: 동일한 계열의 알고리즘들을 캡슐화하여, 클라이언트의 변경 없이 알고리즘을 서로 교환해서 사용할 수 있게 하는 패턴
-
템플릿 메서드 (Template Method)
- 개념: 상위 클래스에서 알고리즘의 전체적인 골격(뼈대)을 정의하고, 하위 클래스에서 세부적인 처리를 구체화하는 패턴
- 코드의 양을 줄이고 유지보수를 용이하게 한다.
- (생성 패턴의 ‘팩토리 메서드’와 유사한 구조)
-
방문자 (Visitor)
- 개념: 객체의 데이터 구조에서 처리 기능(연산)을 분리하여 별도의 클래스로 구성하는 패턴
- 분리된 처리 기능은 각 클래스를 방문(Visit)하여 수행된다.