디자인패턴
: 소프트웨어를 설계할때 특정 맥락에서 자주 발생하는 고질적인 문제들이 발생했을때 재사용할 수 있는 훌륭한 해결책
: "바퀴를 다시 발명하지 마라" 이미 만들어져서 잘 되는 것을 처음부터 다시 만들 필요가 없다는 의미
- 패턴
: 각기 다른 소프트웨어 모듈이나 기능을 가진 다양한 응용 소프트웨어 시스템들을 서로 간에 공통되는 설계 문제가 존재하며 이를 처리하는 해결책 사이에도 공통점이 있는데 이러한 유사점을 패턴이라고 함
- GoF (Gang of Four)
: 소프트웨어 개발 영역에서 디자인패턴을 구체화하고 체계화한 사람들
: 23가지의 디자인패턴을 정리하고 디자인 패턴을 생성, 구조, 행위 3가지로 분류함
- 디자인 패턴을 알고 있을때 장점
: 커뮤니케이션이 짧고 명확해진다
: 더 우아하고 빠른 개발을 할 수 있다 (복잡하고 중복이 많은 코드를 짜지 않을 수 있다)
디자인 패턴 구조
- 콘텍스트 (context)
: 문제가 발생하는 상황. 패턴이 적용될 수 있는 상황을 의미
: 경우에 따라서는 패턴이 유용하지 못한 상황을 나타내기도 함
- 문제 (problem)
: 패턴이 적용되어 해결될 필요가 있는 여러 디자인 이슈들
: 제약 사항과 영향력도 문제 해결을 위해 고려해야함
- 해결 (solution)
: 문제를 해결하도록 설계를 구성하는 요소들과 그 요소들 간 관계, 책임, 협력 관계
: 해결은 반드시 구체적인 구현 방법이나 언어에 의존적이지 않으며, 다양한 상황에 적용할 수있는 일종의 템플릿
디자인 패턴의 종류
- 생성 (Creational) 패턴 (5개)
: 객체 생성에 관련된 패턴
: 객체의 생성과 조합을 캡슐화하여 특정 객체가 생성되거나 변경되어도 전체 로직에 큰 영향을 끼치지 않도록 유연성을 제공
- 구조 (Struntural) 패턴 (7개)
: 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴
: 서로 다른 인터페이스를 지닌 객체들을 묶어 단일 인터페이스를 제공하거나 새로운 기능을 제공하는 패턴
- 행위 (Behavioral) 패턴 (11개)
: 객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴
: 한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 그렇게 하면서 객체 사이의 결합도를 최소화 하는 것이 중점
(생성 패턴) 추상 팩토리 (Abstract Factory)
: 연관성이 있는 객체 군이 여러 개 있을 경우 이들을 묶어 추상화하고 어떤 구체적인 상황이 주어지면 팩토리 객체에서 집합으로 묶은 객체 군을 구현
- 필요성
: 클라이언트에서 특정 객체를 사용할대 팩토리 클래스만을 참조하여 특정 객체에 대한 구현부를 감추어 역할과 구현을 분리 시킬 수 있다
- 현실의 예시
: 모니터, 마우스, 키보드를 묶은 전자 제품군이 있는데, 이들을 삼성 제품이냐, 애플 제품이냐, 로지텍 제품이냐에 따라 집합이 브랜드명으로 갈래가 나뉘는 경우, 복잡하게 묶이는 제품군들을 관리와 확장이 용이하게 패턴화
(생성 패턴) 빌더 (Builder)
: 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만듦
: 점층적 생성자 패턴, 자바 빈즈 패턴에서 진화된 형태
: 별도의 Builder 클래스를 만들어 메서드를 통해 스탭 바이 스탭으로 값을 입력 받은 뒤 최종적으로 build() 메서드로 하나의 인스턴스를 생성하여 반환
- 필요성
: 다양한 타입의 인스턴스를 생성할 수 있어, 클래스의 선택적 매개변수가 많은 상황에서 유용하게 사용됨
- 현실의 예시
: 서브웨이에서 주문할때 빵이나 패티 등 속재료들은 주문하는 사람이 맘대로 결정한다
: 선택적인 속재료들을 유연하게 받아 다양한 샌드위치를 조리
(생성 패턴) 팩토리 메서드 (Factory Method)
: 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성
: 객체를 생성할때 어떤 클래스의 인스턴스를 만들지 서브 클래스에서 결정
: 부모 추상 클래스는 인터페이스에만 의존하고 실제로 어떤 구현 클래스를 호출할지는 서브 클래스에서 구현
- 필요성
: 새로운 구현 클래스가 추가되어도 기존 Factory 코드의 수정 없이 새로운 Product만 추가하여 객체를 생성할 수 있음
- 현실의 예시
: 사용자 관리 프로그램이 있을때 회원 가입에 대한 Factory를 만들고 구글, 네이버, 카카오 인증을 붙혀서 회원가입이 가능하도록 한다
(생성 패턴) 프로토타입 (Prototype)
: 원본 객체를 새로운 객체에 복사하여 필요에 따라 수정하는 메커니즘을 제공
- 필요성
: 객체를 생성하는데 비용이 들고 이미 유사한 객체가 존재하는 경우에 사용
- 현실의 예시
: DB로 부터 가져온 데이터를 프로그램에서 수차례 수정해야하는 요구사항이 있는 경우 매번 객체를 생성하여 DB로 부터 가져오면 비용이 많이 발생한다
: 한 번 DB에 접근하여 데이터를 가져온 객체를 필요에 따라 새로운 객체에 복사하여 데이터 수정 작업을 수행하면 비용을 줄일 수 있다
(생성 패턴) 싱글턴 (Singleton)
: 인스턴스를 오직 1개만 생성하고 이를 return 하는 패턴
- 필요성
: 한 개의 인스턴스만을 고정 메모리에 생성하여 해당 객체에 접근할 때 메모리 낭비를 방지해야하는 경우 (환경설정 / 아이템 창)
- 현실의 예시
: API 요청을 위한 토큰을 매번 생성하는 것은 낭비이기 때문에 토큰을 전역변수로 두고 토큰이 없으면 생성하고 있으면 반환하는 함수를 호출한다
(구조 패턴) 어댑터 (Adapter)
: 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 구현
- 필요성
: 호환되지 않는 타사의 라이브러리를 활용해야하는 경우 이를 호환되도록 구현
- 현실의 예시
: XML 형식의 데이터를 제공받아 처리하는 프로그램이 있는데, 외부 라이브러리가 JSON 데이터를 다룰때
: XML과 JSON 양방향으로 변환이 되는 어댑터 인터페이스를 만들어서 해결
(구조 패턴) 브리지 (Bridge)
: 큰클래스 또는 밀접하게 관련된 클래스들의 집합을 두 개 의 개별 계층 구조 (추상화 및 구현)로 나눈 후 각각 독립적으로 개발할 수 있도록 함
: 추상화와 구현은 독립적으로 반드시 사용할 수 있어야함
- 필요성
: 기존 시스템에 부수적인 새로운 기능들을 지속적으로 추가할 때 기존 프로그램의 변경 없이 기능을 확장
- 현실의 예시
: 어떤 앱이 다양한 리눅스와 윈도에 대해 지원하고 GUI와 API를 가진 경우 이를 각각 연결시켰을때 스파게티 코드가 되기 십상이다
: 이때 추상화(UI)와 구현 계층(API)으로 나눈다
: 추상화 객체는 앱의 드러나는 모습을 제어하고 연결된 구현 객체에 실제 작업들을 위임
: 서로 다른 구현들은 공통 인터페이스를 따르는 한 상호 호환이 가능
: 이에따라 같은 GUI는 리눅스와 윈도에 동시에 작동할 수 있으며, API 관련 클래스들을 건드리지 않고 GUI를 변경할 수 있음
: 다른 운영체제에 대한 지원을 추가하려면 구현 계층 구조 내에서 자식 클래스 생성만으로 해결 가능
(구조 패턴) 컴퍼지트 (Composite)
: 여러 개의 객체들로 구성된 복합 객체와 단일 객체를 클라이언트에서 구별 없이 다루게 하는 패턴
: 객체들의 관계를 트리 구조로 구성하여 부분-전체 계층을 표현
: 사용자는 컴퍼지트 패턴을 통해 단일 객체와 복합 객체 모두 동일하게 다룰 수 있다
- 필요성
: 전체-부분의 관계를 갖는 객체들 사이의 관계를 정의할때 유용
: 클라이언트는 전체와 부분을 구분하지 않고 동일한 인터페이스를 사용
- 현실의 예시
: 그림판에 삼각형, 사각형, 동그라미를 만들고 삼각형과 사각형을 그룹화한 상황
: 여기서 만들어진 도형들을 모두 빨간색으로 색칠하고자 함
: 이때 우리가 채우기 버튼을 누를때 선택하는 것이 어떤 도형인지, 그룹인지 구분하지 않아도 된다
: 그림판에는 도형 하나에 대한 채우기 기능과 그룹에 대한 채우기 기능이 같기 때문
(구조 패턴) 데커레이터 (Decorator)
: 객체의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 해주는 패턴
: 여러 부가 기능을 Decorator 클래스로 정의하고 기본 기능에 붙혀서 사용
- 필요성
: 기본 기능에 추가할 수 있는 기능의 종류가 많은 경우 필요한 데코레이터 객체를 조합함으로써 추가 기능의 조합을 설계
- 현실의 예시
: 기본 도로 기능에 차선 표시, 교통량 표시, 교차로 표시, 단속 카메라 표시의 4가지 추가 기능이 있을때 추가 기능의 모든 조합은 15가지가 된다
: 이때 필요 추가 기능의 조합을 동적으로 구현할 수 있다
(구조 패턴) 퍼사드 (Facade)
: 일련의 저수준 인터페이스들을 하나의 고수준 인터페이스로 묶음
: 클라이언트 객체가 여러 저수준 인터페이스의 동작을 제어하려면 여러 저수준 인터페이스의 메서드들을 일일이 호출해야하는데, 이를 고수준 인터페이스의 메서드 호출 하나로 해결
- 필요성
: 저수준 인터페이스를 바꾸기 위해서 다른 인터페이스를 건드리지 않고 통합 인터페이스의 코드만 건드리면 되므로 클라이언트 객체는 여러 저수준 인터페이스에 대해 의존성이 느슨해짐
- 현실의 예시
: 커피를 만들기 위해서는 원두를 냉동실에서 꺼내고 원두를 갈고, 간 원두를 필터에 놓은 후 뜨거운 물을 만들고 물을 부어서 만들어야 한다
: 이때 커피를 만들기 위한 과정중 원두를 갈고 필터에 놓은 후 뜨거운 물을 붓는 부분을 커피 머신 (고수준 인터페이스)이 맡는다
(구조 패턴) 플라이 웨이트 (Flyweight)
: 인스턴스를 가능한 한 공유해서 사용함으로써 메모리를 절약
- 필요성
: 메모리 절약
- 현실의 예시
: 마인크레프트에서 나무를 1만개를 생성한다고 할때 만들어진 나무들을 저장한다
: 나무를 심을때 저장된 동일한 나무가 있으면 해당 나무를 복사해서 심는다
: 새로운 모양의 나무를 심을때만 새 객체를 생성하는 것
(구조 패턴) 프록시 (Proxy)
: 특정 객체가 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어
- 필요성
: 필요한 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 필요한 시점까지 객체의 생성을 미룰 수 있음
- 현실의 예시
: 신용카드는 은행 계좌의 프록시이고, 은행 계좌는 현금의 프록시이다
(행위 패턴) 책임 연쇄 (Chain of Responsibility)
: 핸들러들의 체인을 따라 요청을 전달할 수 있도록 함
: 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지 결정
- 필요성
: 프로그램이 다양한 방식과 종류의 요청들을 처리할 것으로 예상되지만 정확한 유형들과 순서들을 미리 알 수 없는 경우에 유용
- 현실의 예시
: 온라인 주문 시스템을 개발할 때 인증된 사용자만 주문을 하도록 접근 제어가 필요한 상황
: 주민번호 인증 > 휴대전화 인증 > 이메일 인증을 거칠 때 중간에 실패하면 다음 단계를 거칠 필요가 없음
(행동 패턴) 커맨드 (Command)
: 실행될 기능을 캡슐화 함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴
: 실행될 기능의 변경에도 호출자 클래스를 수정 없이 그대로 사용 가능
- 필요성
: 이벤트가 발생했을때 실행될 기능이 다양하면서도 변경이 필요한 경우, 이벤트를 발생 시키는 클래스를 변경하지 않고 재사용 하고자 할 때 유용
- 현실의 예시
: 어떤 버튼이 있을때 이 버튼에 여러 기능을 부여하고자 한다 (불 켜기, 알람 동작, 창문 열기 까지)
: 이때 버튼이 아닌 버튼과 연결된 배전반의 선만 건드려서 이를 수행하게 한다
(행동 패턴) 인터프리터 (Interpreter)
: 자주 등장하는 문제를 별개의 언어로 정의하고 재사용 하는 패턴
: 반복되는 문제패턴을 언어 또는 문법으로 정의하고 확장
- 필요성
: 사용하는 언어로 해결할 수 없는 문제를 다른 방법으로 해결
- 현실의 예시
: 정규 표현식, 사용하는 언어와 다른 기술 및 도구
(행동 패턴) 이터레이터 (Iterator)
: 컬렉션 구현 방법을 노출시키지 않으면서도 그 집합체 안에 있는 모든 항목에 접근할 수 있는 방법을 제공
- 필요성
: 집합체 내에서 어떤 식으로 작업이 처리되는지 몰라도 그안에 들어있는 항목들에 대해 반복 작업을 수행할 수 있음
- 현실의 예시
: 책꽂이 안에 책을 꽂고 다시 책을 하나씩 확인하는 기계
: 책꽂이를 반복적으로 탐색하고 책을 넣고, 확인하는 과정에 대해서 사람은 알필요가 없다
(행동 패턴) 중재자 (Mediator)
: 객체 간 직접 통신을 제한하고 중재자 객체를 통해서만 협력
: 중재자 객체는 요청에 대해 적절한 컴포넌트로 리다이렉션을 수행
: 컴포넌트들은 단일 중재자 객체에만 의존
- 필요성
: 객체 간의 혼란스러운 의존 관계를 줄일 수 있음
- 현실의 예시
: 항공기 조종사들은 다음에 누가 비행기를 착륙시킬지 결정할때 서로 대화하지 않는다
: 대신 관제탑을 통해 통신이 이루어진다
(행동 패턴) 메멘토 (Memento)
: 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있도록 함
: 메멘토라는 특수 객체에 객체 상태의 복사본을 저장
: 메멘토의 내용에는 메멘토를 생성한 객체를 제외한 다른 어떤 객체도 접근할 수 없음
: 다른 객체는 메멘토들과 제한된 인터페이스를 사용해 통신
- 필요성
: 클래스 간 비공개 공간 침범을 방지
- 현실의 예시
: 택스트 편집기의 Undo 기능
(행동 패턴) 옵저버 (Observer)
: 옵저버가 관찰하고 있는 발행자의 상태가 변화하면 발행자가 각 옵저버들에게 통지하고, 옵저버들은 조치를 취함
: 프로그래밍적으로 관찰 보다는 갱신을 위한 힌트 정보를 전달 받기를 기다리는 것으로 볼 수 있다
: 즉, 관찰 대상 객체로 부터 수동적으로 전달 받기를 대기
- 필요성
: 앱이 한정된 시간, 특정한 케이스에만 다른 객체를 관찰해야하는 경우
: 대상 객체의 상태가 변경될때마다 다른 객체의 동작을 트리거 해야할 때
: 한 객체의 상태가 변경되면 다른 객체도 변경해야할 때 (어떤 객체들이 변경되어야 하는지는 몰라도됨)
: MVC 패턴 (Model과 View의 관계를 Suject와 Observer의 관계. 하나의 Model에 복수의 View가 대응)
- 현실의 예시
: 뉴스 피드나 유튜브에서 채널은 발행자가 되고, 구독자들은 관찰자가 된다
: 유튜버가 영상을 올리면 구독자들에게 알림이 가는 것과 같은 구조
(행동 패턴) 상태 (State)
: 객체가 상태에 따라 행위를 다르게 할 때, 직접 상태를 체크하여 상태에 따른 행위를 호출하는 것이 아니라 상태를 객체화 하여 필요에 따라 다르게 행동하도록 위임
: 객체의 특정 상태 (클래스)
: 상태에 따른 행위 (클래스 내 메서드)
: 상태 클래스를 인터페이스로 캡슐화
- 필요성
: 한 객체가 상태에 따라 동일한 동작을 다르게 수행해야 하는 경우
- 현실의 예시
: 엘리베이터는 상승 / 하강 / 정지의 상태를 가진다
: 이 상태에 따라 문이 열림, 닫힘등을 제어한다
(행동 패턴) 전략 (Strategy)
: 객체들이 할 수 있는 행위 각각에 대해 전략 클래스를 생성하고, 유사한 행위들을 캡슐화 하는 인터페이스를 정의
: 객체의 행위를 동적으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고, 전략을 바꿔주기만 함으로써 행위를 유연하게 확장
- 필요성
: 객체 내 한 알고리즘의 다양한 변형을 사용하고자 할 때
: 런타임중에 한 알고리즘에서 다른 알고리즘으로 전환하고 싶을때
: 일부 행동을 실행하는 방식에서만 차이가 있는 유사한 클래스들이 많은 경우
- 현실의 예시
: 기차와 버스가 있고, 기차는 선로를 따라 이동하고, 버스는 도로를 따라 이동한다
: 이때 선로를 따라 움직이는 버스를 운용할때 전략을 선로 이동으로 바꾸면 선로 이동에 맞게 버스가 움직일 수 있다
(행동 패턴) 템플릿 메서드 (Template Method)
: 어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴
: 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현
- 필요성
: 전체적으로 동일하면서 부분적으로는 다른 구문으로 구성된 메서드의 코드 중복을 최소화 할 때 유용
- 현실의 예시
: 현대 엘리베이터면 현대에서 만든 모터에서 움직임을 제어
: 오티스 엘리베이터면 오티스에서 만든 모터에서 움직임을 제어
: 엘리베이터에게 있어 움직임이라는 동작은 모터에게 달려 있고, 모터는 교체 및 확장이 가능하면서도 기능이 중복되어 있다
(행동 패턴) 비지터 (Visitor)
: 데이터 구조 안을 돌아다니는 주체인 방문자를 나타내는 클래스를 준비해서 처리를 맡김
: 데이터 구조와 처리를 분리하는 패턴
: 새로운 처리를 추가하고 싶을때는 새로운 방문자를 만들고 데이터 구조는 방문자를 받아들이면 된다
- 필요성
: 상속 없이 클래스에 메서드를 효과적으로 추가하기 위해 사용
: 자료구조와 처리 알고리즘을 분리할때 유용
: 데이터 구조보다 알고리즘이 더 자주 바뀔때 유용
- 현실의 예시
: 보험 상담사는 방문한 건물에 있는 회사 또는 조직의 유형에 따라 맞춤형 보험을 추천해야한다
: 주거용 건물을 방문하면 의료 보험을 판매
: 은행을 방문할때는 도난 보험을 판매
: 카페에 방문할때는 화재 및 홍수 보험을 판매
Reference
'Development > Development' 카테고리의 다른 글
REST API 로깅 가이드 라인 (0) | 2023.11.07 |
---|---|
의사 코드 (Pseudo-code) 작성법 (0) | 2023.11.07 |
멋진 API를 만드는 3가지 비결 (0) | 2023.08.19 |
롱런하는 개발자 마인드셋 (0) | 2023.08.19 |
멱등성 (Idempotent)란? (0) | 2023.08.19 |