디자인 패턴(생성 패턴, 구조 패턴, 행동 패턴)
생성 패턴, 구조 패턴, 행동 패턴
생성 패턴(Creational Patterns)
기존 코드의 유연성과 재사용을 증가시키는 다양한 객체 생성 메커니즘들을 제공한다.
팩토리 메서드
부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 한다.
팩토리 메서드는 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴입니다.
팩토리 메서드 패턴은 (new
연산자를 사용한) 객체 생성 직접 호출들을 특별한 팩토리 메서드에 대한 호출들로 대체하라고 제안합니다. 객체들은 여전히 new
연산자를 통해 생성되지만 팩토리 메서드 내에서 호출되고 있습니다. 참고로 팩토리 메서드에서 반환된 객체는 종종 제품이라고도 불립니다.
장점
- 크리에이터와 구상 제품들이 단단하게 결합되지 않도록 할 수 있습니다.
- 단일 책임 원칙. 제품 생성 코드를 프로그램의 한 위치로 이동하여 코드를 더 쉽게 유지관리할 수 있습니다.
- 개방/폐쇄 원칙. 당신은 기존 클라이언트 코드를 훼손하지 않고 새로운 유형의 제품들을 프로그램에 도입할 수 있습니다.
단점
- 패턴을 구현하기 위해 많은 새로운 자식 클래스들을 도입해야 하므로 코드가 더 복잡해질 수 있습니다. 가장 좋은 방법은 크리에이터 클래스들의 기존 계층구조에 패턴을 도입하는 것입니다.
부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 한다.
추상 팩토리 Abstract Factory
추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 한다.
추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성패턴입니다.
추상 팩토리 패턴의 첫 번째 방안은 각 제품 패밀리(제품군)에 해당하는 개별적인 인터페이스를 명시적으로 선언하는 것입니다. (예: 의자, 소파 또는 커피 테이블). 그다음, 제품의 모든 변형이 위 인터페이스를 따르도록 합니다. 추상 공장 패턴의 다음 단계는 추상 팩토리 패턴을 선언하는 것입니다. 추상 팩토리 패턴은 제품 패밀리 내의 모든 개별 제품들의 생성 메서드들이 목록화되어있는 인터페이스입니다. (예: createChair
(의자 생성), createSofa
(소파 생성), createCoffeeTable
(커피 테이블 생성) 등).
다음은 제품 변형을 다룰 차례입니다. 제품 패밀리의 각 변형에 대해 AbstractFactory
추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성합니다. 팩토리는 특정 종류의 제품을 반환하는 클래스입니다. 예를 들어 ModernFurnitureFactory
(현대식 가구 팩토리)에서는 다음 객체들만 생성할 수 있습니다: ModernChair
(현대식 의자), ModernSofa
(현대식 소파) 및 ModernCoffeeTable
(현대식 커피 테이블).
장점
- 팩토리에서 생성되는 제품들의 상호 호환을 보장할 수 있습니다.
- 구상 제품들과 클라이언트 코드 사이의 단단한 결합을 피할 수 있습니다.
- 단일 책임 원칙. 제품 생성 코드를 한 곳으로 추출하여 코드를 더 쉽게 유지보수할 수 있습니다.
- 개방/폐쇄 원칙. 기존 클라이언트 코드를 훼손하지 않고 제품의 새로운 변형들을 생성할 수 있습니다.
단점
- 패턴과 함께 새로운 인터페이스들과 클래스들이 많이 도입되기 때문에 코드가 필요 이상으로 복잡해질 수 있습니다.
빌더 Builder
복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴이다. 이 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있다.
빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다. 이 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있습니다.
빌더 패턴은 자신의 클래스에서 객체 생성 코드를 추출하여 builders라는 별도의 객체들로 이동하도록 제안합니다.
빌더 패턴은 복잡한 객체들을 단계별로 생성할 수 있도록 합니다. 빌더는 제품이 생성되는 동안 다른 객체들이 제품에 접근(access)하는 것을 허용하지 않습니다.
이 패턴은 객체 생성을 일련의 단계들로 정리하며, 객체를 생성하고 싶으면 위 단계들을 builder(빌더) 객체에 실행하면 됩니다. 또 중요한 점은 모든 단계를 호출할 필요가 없다는 것으로, 객체의 특정 설정을 제작하는 데 필요한 단계들만 호출하면 됩니다.
일부 건축 단계들은 제품의 다양한 표현을 건축해야 하는 경우 다른 구현들이 필요할 수 있습니다. 이런 경우 같은 건축 단계들의 집합을 다른 방식으로 구현하는 여러 다른 빌더 클래스를 생성할 수 있으며, 그런 다음 프로세스내에서 이러한 빌더들을 사용하여 다양한 종류의 객체를 생성할 수 있습니다.
디렉터 (관리자)
더 나아가 제품을 생성하는 데 사용하는 빌더 단계들에 대한 일련의 호출을 디렉터 (관리자)라는 별도의 클래스로 추출할 수 있습니다. 디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하는 반면 빌더는 이러한 단계들에 대한 구현을 제공합니다.
디렉터는 작동하는 제품을 얻기 위하여 어떤 건축 단계들을 실행해야 하는지 알고 있습니다.
프로그램에 디렉터 클래스를 포함하는 것은 필수사항은 아닙니다. 당신은 언제든지 클라이언트 코드에서 생성 단계들을 직접 특정 순서로 호출할 수 있습니다. 그러나 디렉터 클래스는 다양한 생성 루틴들을 배치하여 프로그램 전체에서 재사용할 수 있는 좋은 장소가 될 수 있습니다.
또한 디렉터 클래스는 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨깁니다. 클라이언트는 빌더를 디렉터와 연관시키고 디렉터와 생성을 시행한 후 빌더로부터 결과를 얻기만 하면 됩니다.
장점
- 객체들을 단계별로 생성하거나 생성 단계들을 연기하거나 재귀적으로 단계들을 실행할 수 있습니다.
- 제품들의 다양한 표현을 만들 때 같은 생성 코드를 재사용할 수 있습니다.
단점
- 단일 책임 원칙. 제품의 비즈니스 로직에서 복잡한 생성 코드를 고립시킬 수 있습니다.
프로토타입 Prototype
코드를 그들의 클래스들에 의존시키지 않고 기존 객체들을 복사할 수 있도록 한다.
의도
프로토타입은 코드를 그들의 클래스들에 의존시키지 않고 기존 객체들을 복사할 수 있도록 하는 생성 디자인 패턴입니다.
프로토타입 패턴은 실제로 복제되는 객체들에 복제 프로세스를 위임합니다. 패턴은 복제를 지원하는 모든 객체에 대한 공통 인터페이스를 선언합니다. 이 인터페이스를 사용하면 코드를 객체의 클래스에 결합하지 않고도 해당 객체를 복제할 수 있습니다. 일반적으로 이러한 인터페이스에는 단일 clone
메서드만 포함됩니다.
clone
메서드의 구현은 모든 클래스에서 매우 유사합니다. 이 메서드는 현재 클래스의 객체를 만든 후 이전 객체의 모든 필드 값을 새 객체로 전달합니다. 대부분의 프로그래밍 언어는 객체들이 같은 클래스에 속한 다른 객체의 비공개 필드들에 접근(access) 할 수 있도록 하므로 비공개 필드들을 복사하는 것도 가능합니다.
복제를 지원하는 객체를 프로토타입이라고 합니다. 당신의 객체들에 수십 개의 필드와 수백 개의 가능한 설정들이 있는 경우 이를 복제하는 것이 서브클래싱의 대안이 될 수 있습니다.
미리 만들어진 프로토타입은 서브클래싱의 대안이 될 수 있습니다.
프로토타이핑은 다음과 같이 작동합니다. 일단, 다양한 방식으로 설정된 객체들의 집합을 만듭니다. 그 후 설정한 것과 비슷한 객체가 필요할 경우 처음부터 새 객체를 생성하는 대신 프로토타입을 복제하면 됩니다.
실제상황 적용
실제 산업에서의 프로토타입(원기)은 제품의 대량 생산을 시작하기 전에 다양한 테스트를 수행하는 데 사용됩니다. 그러나 프로그래밍의 프로토타입의 경우 프로토타입들은 실제 생산과정에 참여하지 않고 대신 수동적인 역할을 합니다.
세포의 분열
산업 프로토타입들은 실제로 자신을 복제하지 않기 때문에, 프로토타입 패턴에 더 가까운 예시는 세포의 유사분열 과정입니다. 유사분열 후에는 한 쌍의 같은 세포가 형성됩니다. 원본 세포는 프로토타입 역할을 하며 복사본을 만드는 데 능동적인 역할을 합니다.
장점
- 당신은 객체들을 그 구상 클래스들에 결합하지 않고 복제할 수 있습니다.
- 반복되는 초기화 코드를 제거한 후 그 대신 미리 만들어진 프로토타입들을 복제하는 방법을 사용할 수 있습니다.
- 복잡한 객체들을 더 쉽게 생성할 수 있습니다.
- 복잡한 객체들에 대한 사전 설정들을 처리할 때 상속 대신 사용할 수 있는 방법입니다.
단점
- 순환 참조가 있는 복잡한 객체들을 복제하는 것은 매우 까다로울 수 있습니다.
싱글턴 Singleton
싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근(액세스) 지점을 제공하는 생성 디자인 패턴입니다.
실제상황 적용
정부는 싱글턴 패턴의 훌륭한 예입니다. 국가는 하나의 공식 정부만 가질 수 있습니다. 그리고 ‘X의 정부’라는 명칭은 정부를 구성하는 개인들의 신원과 관계없이 정부 책임자들의 그룹을 식별하는 글로벌 접근 지점입니다.
장점
- 클래스가 하나의 인스턴스만 갖는다는 것을 확신할 수 있습니다.
- 이 인스턴스에 대한 전역 접근 지점을 얻습니다.
- 싱글턴 객체는 처음 요청될 때만 초기화됩니다.
단점
- 단일 책임 원칙을 위반합니다. 이 패턴은 한 번에 두 가지의 문제를 동시에 해결합니다.
- 또 싱글턴 패턴은 잘못된 디자인(예를 들어 프로그램의 컴포넌트들이 서로에 대해 너무 많이 알고 있는 경우)을 가릴 수 있습니다.
- 그리고 이 패턴은 다중 스레드 환경에서 여러 스레드가 싱글턴 객체를 여러 번 생성하지 않도록 특별한 처리가 필요합니다.
- 싱글턴의 클라이언트 코드를 유닛 테스트하기 어려울 수 있습니다. 그 이유는 많은 테스트 프레임워크들이 모의 객체들을 생성할 때 상속에 의존하기 때문입니다. 싱글턴 클래스의 생성자는 비공개이고 대부분 언어에서 정적 메서드를 오버라이딩하는 것이 불가능하므로 싱글턴의 한계를 극복할 수 있는 창의적인 방법을 생각해야 합니다. 아니면 그냥 테스트를 작성하지 말거나 싱글턴 패턴을 사용하지 않으면 됩니다.
구조 패턴(Structural Patterns)
객체들과 클래스들을 구조를 유연하고 효율적으로 유지하면서 더 큰 구조로 조립하는 방법을 설명한다.
어댑터 Adapter
인터페이스가 호환되지 않는 클래스들을 함께 이용할 수 있도록, 타 클래스의 인터페이스를 기존 인터페이스에 덧씌운다.
어댑터는 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴입니다.
어댑터는 한 객체의 인터페이스를 다른 객체가 이해할 수 있도록 변환하는 특별한 객체입니다.
어댑터는 변환의 복잡성을 숨기기 위하여 객체 중 하나를 래핑(포장)합니다. 래핑된 객체는 어댑터를 인식하지도 못합니다. 예를 들어 미터 및 킬로미터 단위로 작동하는 객체를 모든 데이터를 피트 및 마일과 같은 영국식 단위로 변환하는 어댑터로 래핑할 수 있습니다.
어댑터는 데이터를 다양한 형식으로 변환할 수 있을 뿐만 아니라 다른 인터페이스를 가진 객체들이 협업하는 데에도 도움을 줄 수 있습니다.
실제상황 적용
해외여행 전과 후의 서류가방.
미국에서 유럽으로 처음 여행을 가서 노트북을 충전하려고 하면 깜짝 놀랄지도 모릅니다. 전원 플러그와 소켓은 국가마다 표준이 달라 미국 플러그가 독일 소켓에 맞지 않을 수 있기 때문입니다. 이 문제는 미국식 소켓과 유럽식 플러그가 있는 전원 플러그 어댑터를 사용하면 해결할 수 있습니다.
브리지 Bridge
추상화와 구현을 분리해 둘을 각각 따로 발전시킬 수 있다.
컴포지트 Composite
0개, 1개 혹은 그 이상의 객체를 묶어 하나의 객체로 이용할 수 있다.
복합체 패턴은 객체들을 트리 구조들로 구성한 후, 이러한 구조들과 개별 객체들처럼 작업할 수 있도록 하는 구조 패턴입니다.
복합체 패턴은 객체 트리의 모든 컴포넌트들에 대해 재귀적으로 행동을 실행할 수 있도록 합니다.
이 접근 방식의 가장 큰 이점은 더 이상 트리를 구성하는 객체들의 구상 클래스들에 대해 신경 쓸 필요도, 또 물건이 단순한 제품인지 내용물이 있는 상자인지 알 필요도 없다는 점입니다. 단순히 공통 인터페이스를 통해 모두 같은 방식으로 처리하시면 됩니다. 당신이 메서드를 호출하면 객체들 자체가 요청을 트리 아래로 전달합니다.
실제상황 적용
군대 구조의 예시.
대부분의 국가에서 군대는 계층구조로 구성되어 있습니다. 군대는 여러 사단으로 구성되며, 사단은 여단의 집합이고, 여단은 소대의 집합이며, 소대는 또 분대로 나누어질 수 있습니다. 마지막으로 분대는 실제 군인들의 작은 집합입니다. 명령들은 계층구조의 최상위에서 내려와 모든 병사가 자신이 수행해야 할 작업을 알게 될 때까지 계층구조의 각 하위 계층으로 전달됩니다.
장점
- 다형성과 재귀를 당신에 유리하게 사용해 복잡한 트리 구조들과 더 편리하게 작업할 수 있습니다.
- 개방/폐쇄 원칙. 객체 트리와 작동하는 기존 코드를 훼손하지 않고 앱에 새로운 요소 유형들을 도입할 수 있습니다.
단점
- 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려울 수 있으며, 어떤 경우에는 컴포넌트 인터페이스를 과도하게 일반화해야 하여 이해하기 어렵게 만들 수 있습니다.
데코레이터 Decorator
기존 객체의 매서드에 새로운 행동을 추가하거나 오버라이드 할 수 있다.
데코레이터는 객체들을 새로운 행동들을 포함한 특수 래퍼 객체들 내에 넣어서 위 행동들을 해당 객체들에 연결시키는 구조적 디자인 패턴입니다.
실제상황 적용
여러 벌의 옷을 입으면 복합 효과를 얻을 수 있습니다.
옷을 입는 것은 데코레이터 패턴을 사용하는 예입니다. 당신은 추울 때 스웨터로 몸을 감쌉니다. 스웨터를 입어도 춥다면 위에 재킷을 입고, 또 비가 오면 비옷을 입습니다. 이 모든 옷은 기초 행동을 ‘확장’하지만, 당신의 일부가 아니기에 필요하지 않을 때마다 옷을 쉽게 벗을 수 있습니다.
장점
- 새 자식 클래스를 만들지 않고도 객체의 행동을 확장할 수 있습니다.
- 런타임에 객체들에서부터 책임들을 추가하거나 제거할 수 있습니다.
- 객체를 여러 데코레이터로 래핑하여 여러 행동들을 합성할 수 있습니다.
단일 책임 원칙. 다양한 행동들의 여러 변형들을 구현하는 모놀리식 클래스를 여러 개의 작은 클래스들로 나눌 수 있습니다.
### 단점
- 래퍼들의 스택에서 특정 래퍼를 제거하기가 어렵습니다.
- 데코레이터의 행동이 데코레이터 스택 내의 순서에 의존하지 않는 방식으로 데코레이터를 구현하기가 어렵습니다.
- 계층들의 초기 설정 코드가 보기 흉할 수 있습니다.
퍼사드 Facade
많은 분량의 코드에 접근할 수 있는 단순한 인터페이스를 제공한다.
퍼사드 패턴은 라이브러리에 대한, 프레임워크에 대한 또는 다른 클래스들의 복잡한 집합에 대한 단순화된 인터페이스를 제공하는 구조적 디자인 패턴입니다.
퍼사드는 움직이는 부분이 많이 포함된 복잡한 하위 시스템에 대한 간단한 인터페이스를 제공하는 클래스입니다. 하위 시스템과 직접 작업하는 것과 비교하면 퍼사드는 제한된 기능성을 제공합니다. 하지만 퍼사드에는 클라이언트들이 정말로 중요하게 생각하는 기능들만 포함됩니다.
퍼사드는 당신의 앱을 수십 가지의 기능이 있는 정교한 라이브러리와 통합해야 하지만 그 기능의 극히 일부만을 필요로 할 때 편리합니다.
실제상황 적용
전화로 주문하기.
전화로 주문하기 위해 매장에 전화를 걸었을 때 전화를 받는 교환원이 바로 상점의 모든 서비스와 부서에 대한 당신의 퍼사드입니다. 이때 교환원은 주문 시스템, 지불 게이트웨이 및 다양한 배송 서비스에 대한 간단한 음성 인터페이스를 제공합니다.
장점
- 복잡한 하위 시스템에서 코드를 별도로 분리할 수 있습니다.
단점
- 퍼사드는 앱의 모든 클래스에 결합된 전지전능한 객체가 될 수 있습니다.
플라이웨이트 Flyweight
다수의 유사한 객체를 생성·조작하는 비용을 절감할 수 있다.
플라이웨이트는 각 객체에 모든 데이터를 유지하는 대신 여러 객체들 간에 상태의 공통 부분들을 공유하여 사용할 수 있는 RAM에 더 많은 객체들을 포함할 수 있도록 하는 구조 디자인 패턴입니다.
플라이웨이트 패턴은 객체 내부에 공유한 상태의 저장을 중단하고, 대신 이 상태를 이 상태에 의존하는 특정 메서드들에 전달할 것을 제안합니다. 고유한 상태만 객체 내에 유지되므로 해당 고유한 상태는 콘텍스트가 다른 곳에서 재사용할 수 있습니다. 이러한 객체들은 공유한 상태보다 변형이 훨씬 적은 고유한 상태에서만 달라지므로 훨씬 더 적은 수의 객체만 있으면 됩니다. 고유한 상태만 저장하는 객체를 플라이웨이트라고 합니다.
장점
- 당신의 프로그램에 유사한 객체들이 많다고 가정하면 많은 RAM을 절약할 수 있습니다.
단점
- 누군가가 플라이웨이트 메서드를 호출할 때마다 콘텍스트 데이터의 일부를 다시 계산해야 한다면 당신은 CPU 주기 대신 RAM을 절약하고 있는 것일지도 모릅니다.
- 코드가 복잡해지므로 새로운 팀원들은 왜 개체(entity)의 상태가 그런 식으로 분리되었는지 항상 궁금해할 것입니다.
프록시 proxy
접근 조절, 비용 절감, 복잡도 감소를 위해 접근이 힘든 객체에 대한 대역을 제공한다.
프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴입니다. 프록시는 원래 객체에 대한 접근을 제어하므로, 당신의 요청이 원래 객체에 전달되기 전 또는 후에 무언가를 수행할 수 있도록 합니다.
프록시 패턴은 원래 서비스 객체와 같은 인터페이스로 새 프록시 클래스를 생성하라고 제안합니다. 그러면 프록시 객체를 원래 객체의 모든 클라이언트들에 전달하도록 앱을 업데이트할 수 있습니다. 클라이언트로부터 요청을 받으면 이 프록시는 실제 서비스 객체를 생성하고 모든 작업을 이 객체에 위임합니다.
프록시는 데이터베이스 객체로 자신을 변장합니다. 프록시는 지연된 초기화 및 결괏값 캐싱을 클라이언트와 실제 데이터베이스 객체가 알지 못하는 상태에서 처리할 수 있습니다.
당신이 클래스의 메인 로직 이전이나 이후에 무언가를 실행해야 하는 경우 프록시는 해당 클래스를 변경하지 않고도 이 무언가를 수행할 수 있도록 합니다. 프록시는 원래 클래스와 같은 인터페이스를 구현하므로 실제 서비스 객체를 기대하는 모든 클라이언트에 전달될 수 있습니다.
실제상황 적용
신용 카드는 현금과 마찬가지로 결제에 사용할 수 있습니다.
신용 카드는 은행 계좌의 프록시이며, 은행 계좌는 현금의 프록시입니다. 둘 다 같은 인터페이스를 구현하며 둘 다 결제에 사용될 수 있습니다. 신용 카드를 사용하는 소비자는 많은 현금을 가지고 다닐 필요가 없어서 기분이 좋습니다. 또한 상점 주인은 거래 수입을 은행에 가는 길에 강도를 당하거나 잃어버릴 위험 없이 계좌에 전자적으로 입금이 되기 때문에 기분이 좋습니다.
장점
- 클라이언트들이 알지 못하는 상태에서 서비스 객체를 제어할 수 있습니다.
- 클라이언트들이 신경 쓰지 않을 때 서비스 객체의 수명 주기를 관리할 수 있습니다.
- 프록시는 서비스 객체가 준비되지 않았거나 사용할 수 없는 경우에도 작동합니다.
- 개방/폐쇄 원칙. 서비스나 클라이언트들을 변경하지 않고도 새 프록시들을 도입할 수 있습니다.
단점
- 새로운 클래스들을 많이 도입해야 하므로 코드가 복잡해질 수 있습니다.
- 서비스의 응답이 늦어질 수 있습니다.
행동 패턴(Behavioral Patterns)
알고리즘들 및 객체 간의 책임 할당과 관련이 있다.
책임연쇄 Chain of responsibility
책임들이 연결되어 있어 내가 책임을 못 질 것 같으면 다음 책임자에게 자동으로 넘어가는 구조이다.
책임 연쇄 패턴은 핸들러들의 체인(사슬)을 따라 요청을 전달할 수 있게 해주는 행동 디자인 패턴입니다. 각 핸들러는 요청을 받으면 요청을 처리할지 아니면 체인의 다음 핸들러로 전달할지를 결정합니다.
실제상황 적용
기술 지원 부서로의 전화는 여러 교환원을 거쳐 이루어질 수 있습니다.
당신은 당신의 컴퓨터에 새 하드웨어를 구매하여 설치했습니다. 당신은 컴퓨터 괴짜이기 때문에 당신의 컴퓨터에는 여러 운영 체제가 설치되어 있습니다. 당신은 이 하드웨어가 지원되는지 확인하기 위해 모든 운영 체제들의 부팅을 시도합니다. 윈도우는 하드웨어를 자동으로 감지하고 활성화합니다. 그러나 당신이 애지중지하는 리눅스 운영 체제는 새 하드웨어와 작업하는 것을 거부합니다. 당신은 약간의 희망을 품고 상자에 적힌 기술 지원 전화번호로 전화하기로 합니다.
가장 먼저 들리는 것은 자동 응답기의 로봇 음성입니다. 이 음성은 다양한 문제에 대한 9가지 인기 있는 솔루션을 제안하지만, 그 중 어느 것도 당신의 문제와 관련이 없습니다. 잠시 후 로봇이 당신을 실제 교환원에게 연결합니다.
그러나 이 교환원도 별로 도움이 될만한 제안을 하지 않습니다. 그는 당신의 문제를 경청하지 않은 채 사용자 설명서에서 발췌한 긴 문장을 계속 인용합니다. ‘컴퓨터를 껐다가 다시 켜 보셨습니까?’ 같은 별 쓸모없는 문구를 10번 이상 들은 후, 당신은 적절한 엔지니어와 연결해 줄 것을 요구합니다.
결국 드디어 교환원은 어둡고 외로운 지하 서버실에서 인간적인 접촉을 갈망했던 엔지니어 중 한 명에게 전화를 연결합니다. 이 엔지니어는 새 하드웨어에 적합한 드라이브를 어디에서 다운받아야 하는지와 리눅스에 설치하는 방법 등을 알려줍니다. 드디어 문제가 해결되었군요! 당신은 기쁜 마음으로 통화를 종료합니다.
장점
- 요청의 처리 순서를 제어할 수 있습니다.
- 단일 책임 원칙. 당신은 작업을 호출하는 클래스들을 작업을 수행하는 클래스들과 분리할 수 있습니다.
- 개방/폐쇄 원칙. 기존 클라이언트 코드를 손상하지 않고 앱에 새 핸들러들을 도입할 수 있습니다.
단점
- 일부 요청들은 처리되지 않을 수 있습니다.
커맨드 command
위의 명령어를 각각 구현하는 것보다는 하나의 추상 클래스에 메서드를 하나 만들고 각 명령이 들어오면 그에 맞는 서브 클래스가 선택되어 실행하는 것이다.
커맨드는 요청을 요청에 대한 모든 정보가 포함된 독립실행형 객체로 변환하는 행동 디자인 패턴입니다. 이 변환은 다양한 요청들이 있는 메서드들을 인수화 할 수 있도록 하며, 요청의 실행을 지연 또는 대기열에 넣을 수 있도록 하고, 또 실행 취소할 수 있는 작업을 지원할 수 있도록 합니다.
실제상황 적용
레스토랑에서 주문하기.
당신은 도시를 한참 걷다가 멋진 레스토랑에 도착하여 창가 테이블에 앉습니다. 친절한 웨이터가 다가와 신속하게 당신의 주문을 받아 종이에 적습니다. 웨이터는 부엌으로 가서 주문을 벽에 붙입니다. 잠시 후 요리사에게 주문이 전달되고 요리사는 주문을 읽고 그에 따라 음식을 요리합니다. 요리사는 주문과 함께 식사를 트레이에 놓습니다. 웨이터는 트레이를 발견한 후 당신이 주문한 대로 식사가 요리되었는지 확인하고 완성된 주문을 당신의 테이블로 가져옵니다.
종이에 적힌 주문은 커맨드 역할을 합니다. 이 주문은 요리사가 요리할 준비가 될 때까지 대기열에 남아 있습니다. 주문에는 식사를 요리하는 데 필요한 모든 관련 정보가 포함되어 있습니다. 이를 통해 요리사는 당신에게서 주문 세부 사항을 직접 전달받는 대신 바로 요리를 시작할 수 있습니다.
장점
- 단일 책임 원칙. 작업을 호출하는 클래스들을 이러한 작업을 수행하는 클래스들로부터 분리할 수 있습니다.
- 개방/폐쇄 원칙. 기존 클라이언트 코드를 손상하지 않고 앱에 새 커맨드들을 도입할 수 있습니다.
- 실행 취소/다시 실행을 구현할 수 있습니다.
- 작업들의 지연된 실행을 구현할 수 있습니다.
- 간단한 커맨드들의 집합을 복잡한 커맨드로 조합할 수 있습니다.
단점
- 발송자와 수신자 사이에 완전히 새로운 레이어를 도입하기 때문에 코드가 더 복잡해질 수 있습니다.
반복자 Iterator
반복이 필요한 자료구조를 모두 동일한 인터페이스를 통해 접근할 수 있도록 메서드를 이용해 자료구조를 활용할 수 있도록 해준다.
반복자는 컬렉션의 요소들의 기본 표현(리스트, 스택, 트리 등)을 노출하지 않고 그들을 하나씩 순회할 수 있도록 하는 행동 디자인 패턴입니다.
- 이터레이터 패턴을 사용하면 집합체 내에서 어떤 식으로 일이 처리되는지 몰라도 그 안에 들어있는 항목들에 대해서 반복작업을 수행 할 수 있다
반복자 패턴의 주 아이디어는 컬렉션의 순회 동작을 반복자라는 별도의 객체로 추출하는 것입니다.
반복자들은 다양한 순회 알고리즘들을 구현합니다. 여러 반복자 객체들이 동시에 같은 컬렉션을 순회할 수 있습니다.
반복자 객체는 알고리즘 자체를 구현하는 것 외에도 모든 순회 세부 정보들(예: 현재 위치 및 남은 요소들의 수)을 캡슐화하며, 이 때문에 여러 반복자들이 서로 독립적으로 동시에 같은 컬렉션을 통과할 수 있습니다.
일반적으로 반복자들은 컬렉션의 요소들을 가져오기 위한 하나의 주 메서드를 제공합니다. 클라이언트는 이 메서드를 더 이상 아무것도 반환하지 않을 때까지 계속 실행할 수 있습니다. 이는 반복자가 모든 요소를 순회했음을 의미합니다.
모든 반복자들은 같은 인터페이스를 구현해야 합니다. 이렇게 하면 적절한 반복자가 있는 한 클라이언트 코드는 모든 컬렉션 유형들 및 순회 알고리즘들과 호환됩니다. 컬렉션을 순회하는 특별한 방법이 필요하면 컬렉션이나 클라이언트를 변경할 필요 없이 새 반복자 클래스를 만들기만 하면 됩니다.
실제상황 적용
도보로 로마를 탐험하는 다양한 방법들
당신은 며칠 동안 로마를 방문하고 주요 명소들을 모두 방문할 계획을 세웠습니다. 그러나 막상 도착한 후 당신은 콜로세움조차 찾지 못하고 제자리를 맴도는 데 많은 시간을 허비할 수 있습니다.
위 사례의 대안으로 스마트폰용 가상 가이드 앱을 구매하여 내비게이션에 사용할 수 있습니다. 이 앱은 똑똑하고 저렴하며 실제 가이드와 달리 원하는 만큼 흥미로운 장소에 머물 수 있도록 합니다.
세 번째 대안은 조금 더 비싸더라도 도시를 잘 아는 현지 가이드를 고용하는 것입니다. 현지 가이드는 당신의 취향에 맞게 여행을 조정하고 모든 명소를 보여주며 흥미진진한 이야기들을 많이 알려줄 수 있습니다. 이 대안을 선택하면 재미는 있겠지만, 더 많은 비용이 들게 될 것입니다.
이 모든 대안들은 (예: 당신이 무작위로 생각해 낸 방향들, 스마트폰 내비게이터, 현지 가이드) 로마의 많은 관광명소에 대해 반복자들로 작동합니다.
장점
- 단일 책임 원칙. 부피가 큰 순회 알고리즘들을 별도의 클래스들로 추출하여 클라이언트 코드와 컬렉션들을 정돈할 수 있습니다.
- 개방/폐쇄 원칙. 새로운 유형의 컬렉션들과 반복자들을 구현할 수 있으며 이들을 아무것도 훼손하지 않은 체 기존의 코드에 전달할 수 있습니다.
- 당신은 이제 같은 컬렉션을 병렬로 순회할 수 있습니다. 왜냐하면 각 반복자 객체에는 자신의 고유한 순회 상태가 포함되어 있기 때문입니다.
- 같은 이유로 당신은 순회를 지연하고 필요할 때 계속할 수 있습니다.
단점
- 당신의 앱이 단순한 컬렉션들과만 작동하는 경우 반복자 패턴을 적용하는 것은 과도할 수 있습니다.
- 반복자를 사용하는 것은 일부 특수 컬렉션들의 요소들을 직접 탐색하는 것보다 덜 효율적일 수 있습니다.
1
2
3
4
5
//캐스트 전후
Iterator<Element> title = map.get("title");
Iterator<Element> title = (Iterator<Element>) map.get("title");
** 중재자 Mediator**
클래스간의 복잡한 상호작용을 캡슐화하여 한 클래스에 위임해서 처리하는 디자인 패턴이다.
중재자는 객체 간의 혼란스러운 의존 관계들을 줄일 수 있는 행동 디자인 패턴입니다. 이 패턴은 객체 간의 직접 통신을 제한하고 중재자 객체를 통해서만 협력하도록 합니다.
중재자 패턴은 서로 독립적으로 작동해야 하는 컴포넌트 간의 모든 직접 통신을 중단한 후, 대신 이러한 컴포넌트들은 호출들을 적절한 컴포넌트들로 리다이렉션하는 특수 중재자 객체를 호출하여 간접적으로 협력하게 하라고 제안합니다. 그러면 컴포넌트들은 수십 개의 동료 컴포넌트들과 결합되는 대신 단일 중재자 클래스에만 의존합니다.
실제상황 적용
항공기 조종사들은 다음에 누가 비행기를 착륙시킬지를 결정할 때 서로 직접 대화하지 않습니다. 모든 통신은 비행기 관제탑을 통해 이루어집니다.
공항 관제 구역으로 들어오거나 그곳을 떠나는 항공기의 조종사들은 서로 직접 통신하지 않습니다. 대신, 그들은 높은 타워에 앉아서 일하는 항공 교통 관제사와 통신합니다. 항공 교통 관제사가 없다면 조종사들은 공항 근처의 모든 비행기의 존재 여부를 인식하고 수십 명의 다른 조종사들로 구성된 위원회와 착륙 우선순위를 논의해야 합니다. 그러면 비행기 충돌 횟수는 아마도 하늘로 치솟을 것입니다.
관제탑은 전체 비행을 관할하지 않습니다. 다만 관련되는 비행기의 수가 조종사에게는 너무 많을 수 있기에 공항 터미널 지역에서만 제약들을 강제하기 위해 존재합니다.
장점
- 단일 책임 원칙. 다양한 컴포넌트 간의 통신을 한곳으로 추출하여 코드를 이해하고 유지 관리하기 쉽게 만들 수 있습니다.
- 개방/폐쇄 원칙. 실제 컴포넌트들을 변경하지 않고도 새로운 중재자들을 도입할 수 있습니다.
- 프로그램의 다양한 컴포넌트 간의 결합도를 줄일 수 있습니다.
- 개별 컴포넌트들을 더 쉽게 재사용할 수 있습니다.
단점
- 중재자는 전지전능한 객체로 발전할지도 모릅니다.
메멘토 Memento
객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있게 해준다.
메멘토는 객체의 구현 세부 사항을 공개하지 않으면서 해당 객체의 이전 상태를 저장하고 복원할 수 있게 해주는 행동 디자인 패턴입니다.
구조
중첩된 클래스들에 기반한 구현
이 패턴의 고전적인 구현은 수많은 인기 프로그래밍 언어(예: C++, C# 및 자바)에서 사용할 수 있는 중첩 클래스에 대한 지원에 의존합니다.
- 오리지네이터 클래스는 자신의 상태에 대한 스냅샷들을 생성할 수 있으며, 필요시 스냅샷에서 자신의 상태를 복원할 수도 있습니다.
- 메멘토는 오리지네이터의 상태의 스냅샷 역할을 하는 값 객체입니다. 관행적으로 메멘토는 불변으로 만든 후 생성자를 통해 데이터를 한 번만 전달합니다.
케어테이커는 ‘언제’ 그리고 ‘왜’ 오리지네이터의 상태를 캡처해야 하는지 뿐만 아니라 상태가 복원돼야 하는 시기도 알고 있습니다.
케어테이커는 메멘토들의 스택을 저장하여 오리지네이터의 기록을 추적할 수 있습니다. 오리지네이터가 과거로 돌아가야 할 때 케어테이커는 맨 위의 메멘토를 스택에서 가져온 후 오리지네이터의 복원 메서드에 전달합니다.
- 이 구현에서 메멘토 클래스는 오리지네이터 내부에 중첩됩니다. 이것은 오리지네이터가 메멘토의 필드들과 메서드들이 비공개로 선언된 경우에도 접근할 수 있도록 합니다. 반면에, 케어테이커는 메멘토의 필드들과 메서드들에 매우 제한된 접근 권한을 가지므로 메멘토들을 스택에 저장할 수는 있지만 그들의 상태를 변조할 수는 없습니다.
중간 인터페이스에 기반한 구현
중첩 클래스들을 지원하지 않는 프로그래밍 언어(예: PHP)에 적합한 대안적 구현 방식이 있습니다.
- 중첩 클래스들이 없는 경우, 당신은 케어테이커들이 명시적으로 선언된 중개 인터페이스를 통해서만 메멘토와 작업할 수 있는 규칙을 만들어 메멘토의 필드들에 대한 접근을 제한할 수 있습니다. 이 인터페이스는 메멘토의 메타데이터와 관련된 메서드들만 선언합니다.
- 반면에 오리지네이터들은 메멘토 객체와 직접 작업하여 메멘토 클래스에 선언된 필드들과 메서드들에 접근할 수 있습니다. 이 접근 방식의 단점은 메멘토의 모든 구성원을 공개(public)로 선언해야 한다는 것입니다.
더 엄격한 캡슐화를 사용한 구현
또 다른 구현이 있는데, 이 구현은 당신이 다른 클래스들이 오리지네이터의 상태를 메멘토를 통해 접근할 가능성을 완전히 제거하고자 할 때 유용합니다.
- 이 구현 방식을 사용하면 여러 유형의 오리지네이터들과 메멘토들을 보유할 수 있습니다. 각 오리지네이터는 그에 상응하는 메멘토 클래스와 함께 작동합니다. 오리지네이터들과 메멘토들은 자신의 상태를 누구에게도 노출하지 않습니다.
- 케어테이커들은 이제 메멘토들에 저장된 상태의 변경에 명시적인 제한을 받습니다. 또 케어테이커 클래스는 복원 메서드가 이제 메멘토 클래스에 정의되어 있으므로 오리지네이터에게서 독립됩니다.
- 각 메멘토는 그것을 생성한 오리지네이터와 연결됩니다. 오리지네이터는 자신의 상태 값들과 함께 자신을 메멘토의 생성자에 전달합니다. 이러한 클래스 간의 긴밀한 관계 덕분에 메멘토는, 오리지네이터가 적절한 세터들을 정의했을 경우, 자신의 오리지네이터의 상태를 복원할 수 있습니다.
장단점
- 캡슐화를 위반하지 않고 객체의 상태의 스냅샷들을 생성할 수 있습니다.
- 당신은 케어테이커가 오리지네이터의 상태의 기록을 유지하도록 하여 오리지네이터의 코드를 단순화할 수 있습니다.
- 클라이언트들이 메멘토들을 너무 자주 생성하면 앱이 많은 RAM을 소모할 수 있습니다.
- 케어테이커들은 더 이상 쓸모없는 메멘토들을 파괴할 수 있도록 오리지네이터의 수명주기를 추적해야 합니다.
- PHP, 파이썬 및 JavaScript와 같은 대부분의 동적 프로그래밍 언어에서는 메멘토 내의 상태가 그대로 유지된다고 보장할 수 없습니다.
다른 패턴과의 관계
- 당신은 ‘실행 취소’를 구현할 때 커맨드와 메멘토 패턴을 함께 사용할 수 있습니다. 그러면 커맨드들은 대상 객체에 대해 다양한 작업을 수행하는 역할을 맡습니다. 반면, 메멘토들은 커맨드가 실행되기 직전에 해당 객체의 상태를 저장합니다.
- 메멘토 패턴을 반복자 패턴과 함께 사용하여 현재 순회 상태를 포착하고 필요한 경우 롤백할 수 있습니다.
- 때로는 프로토타입이 메멘토 패턴의 더 간단한 대안이 될 수 있으며, 이 패턴은 상태를 기록에 저장하려는 객체가 간단하고 외부 리소스에 대한 링크가 없거나 링크들이 있어도 이들을 재설정하기 쉬운 경우에 작동합니다.
옵서버 Observer
어떤 클래스에 변화가 일어났을 때, 이를 감지하여 다른 클래스에 통보해주는 것이다.
옵서버 패턴은 당신이 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴입니다.
옵서버 패턴은 출판사 클래스에 개별 객체들이 그 출판사로부터 오는 이벤트들의 알림들을 구독 또는 구독 취소할 수 있도록 구독 메커니즘을 추가할 것을 제안합니다. 두려워하지 마세요. 그리 복잡하지 않습니다. 실제로 이 메커니즘은 1) 구독자 객체들에 대한 참조의 리스트를 저장하기 위한 배열 필드와 2) 그 리스트에 구독자들을 추가하거나 제거할 수 있도록 하는 여러 공개된(public) 메서드들로 구성됩니다.
구독 메커니즘을 통해 개별 객체들이 이벤트 알림들을 구독할 수 있습니다.
이제 출판사에 중요한 이벤트가 발생할 때마다 구독자 리스트를 참조한 후 그들의 객체들에 있는 특정 알림 메서드를 호출합니다.
실제상황 적용
잡지 및 신문 구독.
당신이 신문이나 잡지를 구독한다면 다음 호가 있는지 확인하러 가게에 갈 필요가 없습니다. 대신 출판사가 발행 직후나 사전에 새 발행물을 구독자의 우편함으로 직접 보냅니다.
출판사는 구독자 리스트를 유지 관리하고 구독자들이 어떤 잡지에 관심 있는지 알고 있습니다. 출판사가 새로운 잡지의 발행호들를 보내는 것을 중단시키고 싶다면 구독자들은 언제든지 이 리스트를 떠날 수 있습니다.
장점
- 개방/폐쇄 원칙. 출판사의 코드를 변경하지 않고도 새 구독자 클래스들을 도입할 수 있습니다. (출판사 인터페이스가 있는 경우 그 반대로 구독자의 클래스들을 변경하지 않고 새 출판사 클래스들을 도입하는 것 역시 가능합니다).
- 런타임에 객체 간의 관계들을 형성할 수 있습니다.
단점
- 구독자들은 무작위로 알림을 받습니다.
해석자 Interpreter
문법 규칙을 클래스화한 구조를 갖는 SQL 언어나 통신 프로토콜 같은 것을 개발할 때 사용한다.
상태 State
객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 한다.
상태 패턴은 객체의 내부 상태가 변경될 때 해당 객체가 그의 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니다. 객체가 행동을 변경할 때 객체가 클래스를 변경한 것처럼 보일 수 있습니다.
실제상황 적용
스마트폰의 버튼들과 스위치들은 장치의 현재 상태에 따라 다르게 행동합니다.
- 스마트폰이 잠금 해제된 상태에서 버튼들을 누르면 다양한 함수들이 실행됩니다.
- 스마트폰이 잠긴 상태에서 아무 버튼이나 누르면 항상 잠금 해제 화면이 나타납니다.
- 스마트폰의 충전량이 적을 때 아무 버튼이나 누르면 충전 화면이 나타납니다.
구조
장점
- 단일 책임 원칙. 특정 상태들과 관련된 코드를 별도의 클래스들로 구성하세요.
- 개방/폐쇄 원칙. 기존 상태 클래스들 또는 콘텍스트를 변경하지 않고 새로운 상태들을 도입하세요.
- 거대한 상태 머신 조건문들을 제거하여 콘텍스트의 코드를 단순화하세요.
단점
- 상태 머신에 몇 가지 상태만 있거나 머신이 거의 변경되지 않을 때 상태 패턴을 적용하는 것은 과도할 수 있습니다.
전략 Strategy
알고리즘 군을 정의하고 각각 하나의 클래스로 캡슐화한 다음, 필요할 때 서로 교환해서 사용할 수 있게 해준다.
전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다.
전략 패턴은 특정 작업을 다양한 방식으로 수행하는 클래스를 선택한 후 모든 알고리즘을 전략들(strategies)이라는 별도의 클래스들로 추출할 것을 제안합니다.
콘텍스트(context)라는 원래 클래스에는 전략 중 하나에 대한 참조를 저장하기 위한 필드가 있어야 합니다. 콘텍스트는 작업을 자체적으로 실행하는 대신 연결된 전략 객체에 위임합니다.
콘텍스트는 작업에 적합한 알고리즘을 선택할 책임이 없습니다. 대신 클라이언트가 원하는 전략을 콘텍스트에 전달합니다. 사실, 콘텍스트는 전략들에 대해 많이 알지 못합니다. 콘텍스트는 같은 일반 인터페이스를 통해 모든 전략과 함께 작동하며, 이 일반 인터페이스는 선택된 전략 내에 캡슐화된 알고리즘을 작동시킬 단일 메서드만 노출합니다.
이렇게 하면 콘텍스트가 구상 전략들에 의존하지 않게 되므로 콘텍스트 또는 다른 전략들의 코드를 변경하지 않고도 새 알고리즘들을 추가하거나 기존 알고리즘들을 수정할 수 있습니다.
실제상황 적용
공항에 도착하기 위한 다양한 전략들.
공항에 가야 한다고 상상해 보세요. 당신은 버스를 탈 수도 있고, 택시나 자전거를 탈 수도 있습니다. 이것들이 바로 당신의 운송 전략들입니다. 예산이나 시간 제약 등을 고려하여 이러한 전략 중 하나를 선택할 수 있습니다.
장점
- 런타임에 한 객체 내부에서 사용되는 알고리즘들을 교환할 수 있습니다.
- 알고리즘을 사용하는 코드에서 알고리즘의 구현 세부 정보들을 고립할 수 있습니다.
- 상속을 합성으로 대체할 수 있습니다.
- 개방/폐쇄 원칙. 콘텍스트를 변경하지 않고도 새로운 전략들을 도입할 수 있습니다.
단점
- 알고리즘이 몇 개밖에 되지 않고 거의 변하지 않는다면, 패턴과 함께 사용되는 새로운 클래스들과 인터페이스들로 프로그램을 지나치게 복잡하게 만들 이유가 없습니다.
- 클라이언트들은 적절한 전략을 선택할 수 있도록 전략 간의 차이점들을 알고 있어야 합니다.
- 현대의 많은 프로그래밍 언어에는 익명 함수들의 집합 내에서 알고리즘의 다양한 버전들을 구현할 수 있는 함수형 지원이 있으며, 클래스들과 인터페이스들을 추가하여 코드의 부피를 늘리지 않으면서도 전략 객체를 사용했을 때와 똑같이 이러한 함수들을 사용할 수 있습니다.
객체의 속성, 기능이 상수이다. 속성 final 기능 static
switch if else 없어야함
String opcode를 Enum operator로 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package strategy;
import java.util.Scanner;
public class Calculator {
public static int calculate(int a, int b, String opcode){
int c = 0;
switch (opcode){
case "+": c = a + b; break;
case "-": c = a - b; break;
case "*": c = a * b; break;
case "/": c = a / b; break;
}
return c;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("정수, 정수, 연산자 ");
System.out.println(Calculator.calculate(sc.nextInt(), sc.nextInt(), sc.next()));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package strategy;
import java.util.Scanner;
enum Operator {
PLUS {
@Override
public int apply(int a, int b) {
return a + b;
}
},
MINUS {
@Override
public int apply(int a, int b) {
return a - b;
}
},
MULTIPLY {
@Override
public int apply(int a, int b) {
return a * b;
}
},
DIVIDE {
@Override
public int apply(int a, int b) {
return a / b;
}
}
;
public abstract int apply(int a, int b);
}
public class Calculator {
public int calculate(int a, int b, Operator operator) {
return operator.apply(a, b);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("정수, 정수, 연산자 ");
Calculator calculator = new Calculator();
System.out.println(calculator.calculate(
sc.nextInt(),
sc.nextInt(),
Operator.valueOf(sc.next().toUpperCase())));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package strategy;
import java.util.Arrays;
import java.util.function.BiFunction;
enum Operator {
PLUS("+", (num1, num2) -> num1 + num2),
MINUS("-", (num1, num2) -> num1 - num2),
MULTIPLY("*", (num1, num2) -> num1 * num2),
DIVIDE("/", (num1, num2) -> num1 / num2);
private String operator;
private BiFunction<Double, Double, Double> expression;
Operator(String operator, BiFunction<Double, Double, Double> expression) {
this.operator = operator;
this.expression = expression;
}
public static double calculate(String operator, double num1, double num2) {
return getOperator(operator).expression.apply(num1, num2);
}
private static Operator getOperator(String operator) {
return Arrays.stream(values())
.filter(o -> o.operator.equals(operator))
.findFirst().orElseThrow(() -> new IllegalArgumentException("올바른 연산자가 아닙니다."));
}
}
public class Calculator {
double calculate(double a, String operator, double b) {
return Operator.calculate(operator, a, b);
}
public static void main(String[] args) {
Calculator c = new Calculator();
double d = c.calculate(1,"+", 2);
System.out.println("Result : "+d);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package strategy;
import java.util.Arrays;
import java.util.Scanner;
import java.util.function.Function;
import java.util.function.Supplier;
/*
대표적인 Enum 메소드
static E values() 해당 열거체의 모든 상수를 저장한 배열을 생성하여 반환함.
static E valueOf(String name) 전달된 문자열과 일치하는 해당 열거체의 상수를 반환함.
protected void finalize() 해당 Enum 클래스가 final 메소드를 가질 수 없게 됨.
String name() 해당 열거체 상수의 이름을 반환함.
int ordinal() 해당 열거체 상수가 열거체 정의에서 정의된 순서(0부터 시작)를 반환함.
* **/
enum Week {
SUNDAY("sunday", ()-> "일요일"),
MONDAY("monday", ()-> "월요일"),
TUESDAY("tuesday", ()-> "화요일"),
WEDNESDAY("wednesday", ()-> "수요일"),
THURSDAY("thursday", ()-> "목요일"),
FRIDAY("friday", ()-> "금요일"),
SATURDAY("saturday", ()-> "토요일")
;
private String week;
private Supplier<String> supplier;
Week(String week, Supplier<String> supplier) {
this.week = week;
this.supplier = supplier;
}
public static String getKoreanDay(String week) {
return getOperator(week).supplier.get();
}
private static Week getOperator(String day) {
return Arrays.stream(values())
.filter(o -> o.week.equals(day))
.findFirst().orElseThrow(() -> new IllegalArgumentException("올바른 값이 아닙니다."));
}
}
public class WhatDay{
String getKoreanDay(String week) {
return Week.getKoreanDay(week);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("무슨 요일");
WhatDay w = new WhatDay();
String s = w.getKoreanDay(sc.next());
System.out.println(s);
}
}
템플릿 메서드 Template method
상위 클래스에서는 추상적으로 표현하고 그 구체적인 내용은 하위 클래스에서 결정되는 디자인 패턴이다.
템플릿 메서드는 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드(재정의)할 수 있도록 하는 행동 디자인 패턴입니다.
실제상황 적용
일반적인 건축 계획은 클라이언트의 니즈에 더 잘 부합하도록 약간 변경될 수 있습니다.
템플릿 메서드 접근 방식은 대량 주택 건설에 사용할 수 있습니다. 표준 주택 건설을 위한 건축 계획에는 잠재적 주택 소유자가 결과 주택의 일부 세부 사항들을 조정할 수 있도록 하는 여러 확장 지점들이 포함될 수 있습니다.
완성된 집이 다른 집들과 조금씩 다르도록 각 건축 단계(예: 기초 쌓기, 골조공사, 벽 쌓기, 수도 및 전기 배선 설치 등)는 약간씩 변경될 수 있습니다.
장점
- 클라이언트들이 대규모 알고리즘의 특정 부분만 오버라이드하도록 하여 그들이 알고리즘의 다른 부분에 발생하는 변경에 영향을 덜 받도록 할 수 있습니다.
- 중복 코드를 부모 클래스로 가져올 수 있습니다.
단점
- 일부 클라이언트들은 알고리즘의 제공된 골격에 의해 제한될 수 있습니다.
- 당신은 자식 클래스를 통해 디폴트 단계 구현을 억제하여 리스코프 치환 원칙을 위반할 수 있습니다.
- 템플릿 메서드들은 단계들이 더 많을수록 유지가 더 어려운 경향이 있습니다.
방문자 visitor
각 클래스의 데이터 구조로부터 처리 기능을 분리하여 별도의 visitor 클래스로 만들어놓고 해당 클래스의 메서드가 각 클래스를 돌아다니며 특정 작업을 수행하도록 하는 것이다.
비지터(방문자) 패턴은 알고리즘들을 그들이 작동하는 객체들로부터 분리할 수 있도록 하는 행동 디자인 패턴입니다.
비지터 패턴은 당신이 새로운 행동을 기존 클래스들에 통합하는 대신 visitor(방문자)라는 별도의 클래스에 배치할 것을 제안합니다. 이제 행동을 수행해야 했던 원래 객체는 visitor의 메서드 중 하나에 인수로 전달됩니다. 그러면 메서드는 원래 객체 내에 포함된 모든 필요한 데이터에 접근할 수 있습니다.
실제상황 적용
좋은 보험 대리인은 항상 다양한 유형의 조직들에 적절한 보험을 판매할 준비가 되어 있습니다.
새로운 고객을 확보하고 싶어 하는 노련한 보험 대리인을 상상해 봅시다. 그는 근방의 모든 건물을 방문하여 만나는 모든 사람에게 보험을 판매하려고 합니다. 그는 방문한 건물에 있는 회사 또는 조직의 유형에 따라 맞춤형 전문 보험 정책들을 제공할 수 있습니다.
- 주거용 건물을 방문할 때는 의료 보험을 판매합니다.
- 은행을 방문할 때는 도난 보험을 판매합니다.
- 커피숍을 방문할 때는 화재 및 홍수 보험을 판매합니다.
장점
- 개방/폐쇄 원칙. 당신은 다른 클래스를 변경하지 않으면서 해당 클래스의 객체와 작동할 수 있는 새로운 행동을 도입할 수 있습니다.
- 단일 책임 원칙. 같은 행동의 여러 버전을 같은 클래스로 이동할 수 있습니다.
- 비지터 객체는 다양한 객체들과 작업하면서 유용한 정보를 축적할 수 있습니다. 이것은 객체 트리와 같은 복잡한 객체 구조를 순회하여 이 구조의 각 객체에 비지터 패턴을 적용하려는 경우에 유용할 수 있습니다.
단점
- 당신은 클래스가 요소 계층구조에 추가되거나 제거될 때마다 모든 비지터를 업데이트해야 합니다.
- 비지터들은 함께 작업해야 하는 요소들의 비공개 필드들 및 메서드들에 접근하기 위해 필요한 권한이 부족할 수 있습니다.
출처 ㅣ 디자인 패턴 목록