개발자로서 많은 프로그램 개발을 진행하다 보면 비슷한 코드들이 반복되는 상황을 겪게 된다. 그러다보면 같은 기능을 가진, 비슷한 프로세스로 반복되어 사용되는 코드들을 모아 패턴화시켜 사용하게 되는데, 이를 우리는 디자인 패턴이라고 부른다. 객체지향 프로그래밍에 있어 디자인 패턴은 문제 해결 방법론으로 자주 사용되는데, 이에 대해 자세히 알아보자.
목 차
패턴(Pattern)
- Pattern - 되풀이되는 사건이나 물체의 형태
패턴은 그 어원의 기본 의미를 바탕으로 많은 분야에서 사용되고 있다.
미술의 관점에서 볼 때 패턴은 어떤 이미지가 반복됨을 의미하며, 영문법의 관점에서는 반복되는 어구 또는 문장 구조를 의미하기도 한다.
디자인 패턴이란
IT 개발의 관점에서 디자인 패턴은 소프트웨어 설계와 개발 과정에서 반복되어 자주 발생하는 문제를 해결하기 위한 일종의 템플릿이나 프로토타입을 말한다.
디자인 패턴은 여러 개발자와 팀이 공통 프로젝트 개발을 진행하며 같은 문제를 맞닥뜨렸을 때, 모든 개발자들이 일관적인 접근 방식을 사용하여 문제 해결의 표준화를 실현시킬 수 있다.
디자인 패턴의 사용은 개발의 복잡성을 줄여 재사용성과 유지 보수성을 높이는 솔루션으로 작용한다. 이를 통해 개발자들은 더 효율적이고 안정적인 소프트웨어를 만들 수 있으며, 팀원 간의 협업을 더욱 원활하게 한다. 결국, 패턴의 사용이 장기적으로 소프트웨어 프로젝트의 품질과 생산성을 향상시키는 중요한 요소가 되는 것이다.
디자인 패턴의 기본 원칙
디자인 패턴은 객체 지향 프로그래밍의 객체 지향 특성과 설계 원칙(SOLID)을 바탕으로 구축된다.
- 객체 지향 프로그래밍을 실현하기 위한 4가지 특성 : 캡슐화, 상속, 추상화, 다형성
- 객체지향 설계원칙 :
- 단일 책임 원칙 (Single Responsibility Principle, SRP) : 클래스는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
- 개방-폐쇄 원칙 (Open-Closed Principle, OCP) : 클래스는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
- 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) : 하위 클래스는 언제나 상위 클래스를 대체할 수 있어야 한다.
- 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
- 의존 역전 원칙 (Dependency Inversion Principle, DIP) : 상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
객체 지향의 특성과 설계 원칙에 대해 더 궁금증이 생긴다면, 다음 포스팅에서 자세히 알아 보도록 하자. (*포스팅 예정!)
디자인 패턴의 분류 및 종류
디자인 패턴은 1994년, *Gang of Four(이하 GoF)의 책인 "Design Patterns: Elements of Reusable Object-Oriented Software"에서 23개의 주요 디자인 패턴을 설명하며 유명해졌다. 디자인 패턴을 논할 때 이들의 정의한 패턴를 베이스로 이야기 할 수 있는데, 앞으로 차근차근 포스팅하면서 각 패턴에 대해서 상세히 공부해 볼 예정이다.
*Gang of Four : Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides로 구성된 개발자 그룹. 줄여서 GoF로 불린다. |
디자인 패턴은 일반적으로 생성 패턴, 구조 패턴, 행위 패턴 이 3가지로 분류된다. 각 분류는 패턴이 어떤 목적을 가지고 있으며, 코드에 어떤 방식으로 영향을 미치는지에 따라 구분된다.
1. 생성 패턴 (Creational Patterns)
생성 패턴은 객체 생성 메커니즘을 다루며, 객체의 생성 방법을 제어하는 패턴들을 말한다. 이는 객체 생성을 보다 용이하게 해주고 객체를 필요에 따라 동적으로 생성할 수 있도록 도와준다.
- 싱글톤 패턴 (Singleton Pattern) : 클래스의 인스턴스가 오직 하나만 존재하도록 보장한다. 전역 변수로 접근하도록 하여 인스턴스를 생성하는 로직을 중앙 집중화시킨다.
( 예시 : 설정 정보나 로그 관리 객체 등 ) - 팩토리 메서드 패턴 (Factory Method Pattern) : 객체 생성 로직을 하위 클래스에 위임하여, 클라이언트가 어떤 클래스의 인스턴스를 만들지 결정할 수 있도록 한다.
( 예시 : 특정 분류 안에 다양한 제품군을 생성하는 경우 ) - 추상 팩토리 패턴 (Abstract Factory Pattern) : 관련된 객체 집합을 생성하는 인터페이스를 제공한다. 클라이언트는 구체적인 클래스에 의존하지 않고, 이 패턴을 이용해 다양한 제품군을 생성할 수 있다.
( 예시 : GUI 라이브러리에서 다양한 버튼과 텍스트 박스를 생성할 때 ) - 빌더 패턴 (Builder Pattern): 복잡한 객체 구현을 단계적으로 나누어(단순화) 유연하게 객체를 생성할 수 있도록 한다. 객체 생성 과정이 복잡하고 다양한 구성 요소를 포함할 때 사용한다.
( 예시 : 복잡한 리포트 객체를 단계별로 생성하며, 각 단계를 추가하거나 변경할 수 있게 구현 ) - 프로토타입 패턴 (Prototype Pattern) : 기존 객체를 복제하여 새로운 객체를 생성한다. 복사할 객체의 초기 상태를 기준으로 새로운 객체를 생성한다.
( 예시 : 객체의 생성 비용이 클 때, 기존 객체를 복사하여 사용 )
2. 구조 패턴 (Structural Patterns)
구조 패턴은 객체와 클래스 간의 관계를 설정하여 더 큰 구조를 형성하고, 코드가 더욱 유연하고 재사용이 가능하도록 해준다.
- 어댑터 패턴 (Adapter Pattern) : 기존 인터페이스를 다른 인터페이스로 변환하여, 서로 호환되지 않는 클래스들이 함께 작동할 수 있도록 한다.
( 예시 : 새 시스템과 기존 시스템 간의 데이터 호환성을 위해 인터페이스 변환 ) - 데코레이터 패턴 (Decorator Pattern) : 객체에 추가적인 기능을 동적으로 추가할 수 있다. 기존 객체의 기능을 변경하지 않고도 추가 기능을 적용할 수 있습니다.
( 예시 : 텍스트 편집기에서 다양한 스타일을 적용할 때 ) - 프록시 패턴 (Proxy Pattern) : 특정 객체에 접근할 때 중간에서 대리자 역할을 하여, 객체에 대한 접근을 제어한다.
( 예시 : 접근 제어나 지연 로딩 등 원본 객체 접근을 제어가 필요할 때) - 컴포지트 패턴 (Composite Pattern): 객체를 트리 구조로 구성하여 부분-전체 계층을 구성하여, 개별 객체(부분)와 복합 객체(전체)를 동일하게 다룰 수 있도록 한다.
( 예시 : 파일 시스템의 파일과 폴더 구조 → C:/temp/folder와 C:/temp/hello.txt는 동일 선상의 객체로 다루어 짐) - 퍼사드 패턴 (Facade Pattern): 서브시스템에 대한 간단한 인터페이스를 제공하여, 복잡한 시스템을 쉽게 사용할 수 있게 만든다.
( 예시 : 대규모 라이브러리의 주요 기능만을 모아 단순화된 API를 제공) - 브리지 패턴 (Bridge Pattern) : 구현부와 추상부를 분리하여 독립적으로 확장할 수 있도록 한다. 기능과 구현을 분리하여 각각 독립적으로 확장해야 할 때 사용한다.
( 예시 : 그래픽 앱에서 OS별 구현을 추상화하여 쉽게 교체할 수 있도록 구성.) - 플라이웨이트 패턴 (Flyweight Pattern) : 메모리를 절약하기 위해 객체를 공유하여 사용한다.
( 예시: 텍스트 편집기에서 동일한 글자 모양 객체를 공유)
3. 행동 패턴 (Behavioral Patterns)
행위 패턴은 객체 간의 상호작용과 책임을 정의하기 위해 사용된다. 객체가 어떻게 상호작용하고 통신하는지를 설명한다.
- 옵저버 패턴 (Observer Pattern) : 객체 상태가 변경될 때, 관련된 모든 객체에 전달한다. 주로 이벤트 시스템에 사용된다.
( 예시: GUI 컴포넌트에서 이벤트 리스너 ) - 전략 패턴 (Strategy Pattern) : 알고리즘을 캡슐화하여 실행 중에 동적으로 선택할 수 있도록 한다. 알고리즘의 교환이 용이해진다.
( 예시: 다양한 정렬 알고리즘을 제공할 때 ) - 커맨드 패턴 (Command Pattern) : 요청(명령)을 객체로 만들어 요청에 따라 실행 되도록 하는 패턴이다. 작업을 실행하는 객체와 요청하는 객체를 분리한다.
( 예시 : 버튼 클릭 이벤트 처리 시,각 버튼에 따라 다른 명령 객체 실행 ) - 템플릿 메서드 (Template Method) : 먼저 알고리즘의 골격을 정의하고, 세부 단계를 하위 클래스에서 구현할 수 있도록 한다. 알고리즘의 큰 흐름은 같지만, 세부 단계는 다를 때 사용한다.
( 예시 : 데이터베이스의 CRUD 작업, 쇼핑몰의 주문 처리 등 일련의 정해진 처리 과정에 있고 각 과정마다 구체적 작업이 다를 때 ) - 상태 패턴 (State Pattern) : 상태를 클래스로 정의 하여 객체의 상태에 따라 각각 다른 작업이 실행되도록 한다.
( 예시 : 게임 캐릭터의 상태(전투, 방어 등) 따른 행동 및 포지션이 달라짐) - 중재자 패턴 (Mediator Pattern) : 객체 간의 통신을 중재자가 담당하여 객체 간의 결합도 및 의존도를 줄인다.
( 예시 : 채팅 앱에서 각 유저는 중재 컴포넌트를 통해 메시지를 주고 받음 ) - 인터프리터 패턴 (Interpreter Pattern) : 언어의 문법을 정의하고 해석하는 방식을 제공한다. 특정 언어로 쓰여진 문장에 대해 문법을 해석하고 실행해야 할 때 사용한다.
( 예시 : 수학적 표현식을 해석하는 계산기 ) - 이터레이터 패턴 (Iterator Pattern) : 컬렉션 요소에 순차적으로 접근하여 처리하는 패턴이다.
( 예시 : 리스트나 배열에서 요소를 하나씩 순차적으로 처리 ) - 책임 연쇄 (Chain of Responsibility) : 요청을 처리할 수 있는 연쇄적인 객체들의 연결을 구성하여, 각 객체가 자신이 처리할 수 있으면 처리하고, 그렇지 않으면 다음 객체로 전달하는 방식이다.
( 예시 : 고객 지원 요청이 접수되면, 상담원 → 관리자 → 부서장 순서로 전달되며, 각 역할이 자신이 처리할 수 있는 요청만을 처리하고, 요청이 해결되지 않으면 다음 사람에게 전달) - 메멘토 (Memento) : 객체의 상태를 캡슐화하여 저장하고 복원할 수 있는 기능을 제공한다. 객체의 내부 상태를 노출하지 않고도 상태를 저장하고 이전 상태로 되돌리는 것이 가능하다.
( 예시 : 문서 편집기의 실행 취소(undo) 기능 ) - 방문자 (Visitor) : 객체의 구조를 변경하지 않고 새로운 기능을 추가하려고 할 때, 방문자가 구조 내부 각 요소들을 방문(Visit)하여 기능을 수행하도록 만드는 패턴이다.
( 예시 : 은행 시스템에서 고객의 계좌 타입에 따라 다양한 연산(이자 계산, 잔액 확인, 입출금 내역 출력 등)을 수행할 때 )
'_ 개발' 카테고리의 다른 글
[DESIGN PATTERN/JAVA] Iterator - 이터레이터 패턴 (1) | 2024.11.10 |
---|---|
[DESIGN PATTERN/JAVA] Factory Method - 팩토리 메서드 패턴 (1) | 2024.11.09 |
객체 지향 설계 원칙 5가지 - SOLID (2) | 2024.11.08 |
[DESIGN PATTERN/JAVA] Singleton - 싱글톤 패턴 (4) | 2024.11.01 |
Mac 팁 - 내가 보려고 만든 맥북 단축키 #1 기본 단축키 (1) | 2024.07.26 |