23가지의 개발 패턴 정리하기 (참고 https://refactoring.guru/)
Creational Patterns (생성 패턴)
- Factory Method
- 객체 생성을 위한 인터페이스를 제공하지만 클래스의 인스턴스 생성의 타입을 서브클래스가 결정하도록하는 방식
- 새로운 서브 클래스 추가의 필요시 전체적인 코드의 수정이 필요하다. 서브 클래스의 지속적인 추가시 지속적으로 발생할 가능성이 있다.
- new 연산자 사용 대신에 factory method로 대체하여 사용한다. new 연산자는 factory method에서 호출 시킨다. factory는 공통된 인터페이스를 상속하고 있어야한다.
- Abstract Factory
- 추상화를 통해 구제척인 클래스를 특정하지 않고 비슷한 관련 객체를 만드는 방식
- 새로운 객체를 생성시 이전에 추상화한 것이 있는지 확인이 필요하다.
- 연관된 클래스를 업데이트시 프로그램을 변경하여야한다.
- factory 하나가 연관된 객체들을 만들도록 각 factory에 객체 생성을 맡긴다.
- 모든 factory에 공통된 인터페이스를 선언하여 어떤 factory에서 객체가 만들어 졌는지 알 필요없게한다.
- Builder
- 복잡한 객체를 단계별로 각각 만들며 동일한 생성 코드를 사용하여 객체의 다양한 type을 생성하는 방식
- 복잡한 type을 결정하기 위한 많은 양의 변수가 존재하고 정보가 코드 전체에 흩어져 있을 수 있다. 모든 변수를 전부다 사용한 것이 아니기 때문에 가독성이 떨어 질 수 있다.
- 각 클래스에서 객체 생성 코드를 빌더 객체로 옮겨서 관리한다. (공통된 부분을 옮겨 관리)
- Prototype
- 기존에 만들어진 객체의 클래스와는 독립적으로 해당하는 객체를 복사하는 방식
- 접근지정자 때문에 복제할 객체의 모든 변수의 정보를 복제 할 수 없을 수 있다.
- 만들어진 객체의 정보를 알아야 하기 때문에 실제 클래스에 의존적으로 코드를 만들 수 있다. 또한 인터페이스는 알고 있지만 구체적인 클래스의 내용을 모를 수 있다.
- 이미 생성된 객체 안에 복사 과정을 인터페이스로 상속 시켜 객체를 복사 할 수 있도록 한다.
- Singleton
- 클래스에 하나의 인스턴스만 존재하도록 하고 해당 인스턴스를 글로벌하게 접근하여 사용하도록 하는 방식
- 클래스가 하나의 인스턴스만을 가져야한다.
- 글로벌 인스턴스를 사용하기 때문에 변수의 내용이 덮어씌여질 수도 있다.
- 다른 객체가 new 연산자를 사용하지 못하도록 생성자를 private으로 설정한다.
- 생성자를 대신하는 생성 함수를 만든다. 함수는 내부적으로 private 생성자를 호출하여 객체를 만들고 static 필드에 저장하여 캐시된 객체를 반환하는 방식으로 구현한다.
Structural Patterns (구조 패턴)
- Adapter
- 호환이 되지 않는 인터페이스를 가진 객체들이 함께 동작하도록 하는 방식
- 클래스가 다르다면 기존의 코드로는 사용이 불가능하다.
- 사용이 불가능한 객체를 사용 할 수 있는 객체로 변환 시켜주는 adapter 객체를 만들어 객체를 변환시켜 기존의 코드에 호환되도록한다.
- Bridge
- 큰 클래스 또는 밀접하게 관련된 클래스 집합을 서로 독립적으로 개발할 수 있는 두 개의 개별 계층으로 분하는 방식 (추상화와 구현을 분리하여 별도의 클래스 구현)
- 새로운 type을 추가하려면 하위 클래스를 계속 추가해야 해서 코드의 가독성이 떨어진다
- 하나의 클래스에 다른 클래스의 상태와 동작도 가지게 하여서 이를 참조하여 객체를 생성할 수 있게 한다.
- Composite
- 객체들의 관계를 트리구조로 구성하여 개별 객체인 것 처럼 작업하는 방식
- 핵심 모델을 트리로 나타낼 수 있을 때만 사용할 수 있다
- 공통된 인터페이스를 사용하여 각각의 객체들이 독립적으로 작동하도록한다.
- Decorator
- 객체 결합을 통해 기능을 유연하게 확장 하도록 하는 방식
- 기능들을 통합하거나 추가하는 과정에서 코드의 복잡해지고 중복되는 부분이 많이 발생하게 된다
- 공통된 기능을 Decorator 클래스로 묶어서 이를 상속하여서 기능을 확장한다
- Facade
- 라이브러리, 프레임 워크, 복잡한 클래스 집합에 대하여 단순화된 인터페이스를 제공하는 방식
- 라이브러리, 프레임 워크 등 필요한 클래스 집합의 디테일한 내용을 알 수 없어 이해하기 어렵고 유지와 관리가 어렵다.
- 제한된 기능을 제공 하지만 실질적으로 필요한 부분을 포함하기 때문에 여러가지 라이브러리를 앱에 통합하는데 편리하다.
- Flyweight
- 각 객체의 모든 데이터를 유지하지 않고 여러 객체 간의 공통된 부분을 공유하여 동일한 RAM에 더 많은 객체를 유지 할 수 있는 방식
- 많은 양의 객체가 생성되고 동작되어 RAM의 한계를 넘어버려서 환경에 따라 프로그램을 실사용하지 못함
- 여러 객체의 고정되어 있는 공통 값을 외적인 속성 클래스로 묶어서 개별 객체는 객체별 유니크한 정보를 가지는 속성만 가지도록 한다.
- Proxy
- 실제 기능을 수행하는 객체 대신에 가상의 객체를 사용하여 흐름을 제어하는 방식
- 생성의 시간과 사용 리소스의 크기가 큰 객체가 있을 경우 모든 부분에서 초기화를 실시하여야하지만 이를 실행하는 것이 실질적으로 가능하지 않다.
- 원래의 서비스 객체와 동일한 인터페이스로 프록시 클래스를 생성하여 프록시 객체가 모든 클라이언트에 전달 되도록하여 클라이언트의 요청을 프록시 객체가 실 서비스 객체를 생성하고 작업을 하도록한다.
Behavioral Patterns (행동 패턴)
- Chain of Responsibility
- 연결된 핸들러를 통하여 요청을 다음 핸들러에 전달하거나 직접 처리하는 방식
- 취약점 방어, 코드의 권한 인증 등등의 서비스와 직간접적으로 관련된 기능이 추가되면 추가 될 수 록 코드가 난잡해지고 새로운 검사를 하기 위해서 기존의 코드를 수정해야하는 경우가 생긴다.
- 각각의 기능에 대한 핸들러 객체를 생성하여서 하나의 아웃풋이 다음 핸들러의 인풋이 되도록하여서 동작이 완료되거나 예외가 발생할때까지 전달되도록한다. 이때 모든 핸들러는 같은 인터페이스를 구현하도록하는 것이 중요하다.
- Command
- 요청을 요청에 대한 모든 정보를 가진 객체로 캡슐화하여 실행 할 수 있도록 하는 방식
- 같은 기능에 대하여 여러가지 입력 방식이 존재 할 수 있기 때문에 이에 대한 동작 코드를 각각의 버튼, 입력에 대한 하위 클래스에 중복 코드가 발생하거나 종속된 기능을 만들어야 한다.
- 모든 작업에 대한 command 클래스를 구현하여 각각의 작업과 이어진 하위 클래스와 연결하여 하위 클래스의 동작이 들어오면 연결된 command 클래스가 작업을 처리하도록 하여 중복되는 코드 발생을 막는다.
- Iterator
- 자료 구조를 알려주지 않고 컬렉션의 구성 요소를 순회 할 수 있는 동작 방식
- 컬렉션은 다양한 자료 구조를 가지를 수 있기 때문에 이를 순회하기 위한 여러가지 방식의 알고리즘을 추가하는 것은 효율적인 데이터 저장이라는 본질이 흐려진다.
- 컬랙션들의 순회 동작을 iterator라는 별도의 객체로 추출하여서 사용한다. iterator 객체 안에는 현재 위치 및 끝까지 남은 수 등의 탐색 정보를 캡슐화하여서 사용한다. 모든 iterator는 동일한 인터페이스를 구현한다.
- Mediator
- 객체간의 직접 통신을 제한하고 mediator 객체를 통해서만 함께 동작도록하여 객체간의 복잡한 종속성을 줄일 수 있는 방식
- 동작 클래스들이 다대다 관계로 복잡하게 연결되어 있어 클래스의 재사용과 클래스간의 상호 작용이 어렵다.
- 모든 클래스 상호 작용을 캡슐화하여 하나의 클래스로 만들어 처리하도록 한다.
- Memento
- 객체의 상태 정보를 가지는 클래스를 따로 생성하여 객체의 상태를 저장하고 이전 상태로 복원 할 수 있게 해주는 방식
- 객체의 내용에는 비공개 정보가 존재하고 클래스를 리팩토링하거나 필드의 변화가 생기면 객체의 상태를 복사하는 클래스를 변경하여야한다. 이로 인해 클래스의 정보를 모두 공개하여 취약점을 만들거나 제한하여 복제 구간을 만들지 못 하게한다. undo를 구현 할 수가 없을 수 있다.
- snapshot을 생성을 동작 클래스에서 수행하고 객체의 상태를 유지하고 있을 수 있는 memento라는 객체에 전달하여 객체의 상태를 지속적으로 저장하도록 한다. 이를 스택으로 관리하여 undo를 구현한다.
- Observer
- 객체의 상태 변화를 관찰하는 객체를 생성하여 사용하는 방식, 다른 객체에 알림을 보내는 방식으로 사용할 수 있다.
- 기능, 상품 등에 관심이 있는 사용자가 수시로 확인하여 리소스 낭비가 발생할 수 있다.
- observer 객체를 만들어 특정 객체에 변경 사항이 발생하였을 때 특정 객체에 관심이 있는 다수의 다른 객체에 알리는 역할을 하게 한다.
- State
- 객체의 내부의 상태가 변경되었을 때 동작을 변경 할 수 있도록 하는 방식
- 프로젝트의 규모가 커질 수록 조건을 기반으로 하여 상태에 따라 동작을 정하는 방식은 유지 관리가 어렵고 모든 상황에 대한 동작을 예상하고 구축하기가 힘들어진다.
- 객체에 특정 상태를 클래스로 선언하여 해당하는 동작들을 메서드로 정의하여 클라이언트가 호출하여 사용하도록한다. 모든 클래스는 동일한 인터페이스를 따르도록한다.
- Strategy
- 여러 알고리즘을 클래스별로 캡슐화하여 필요할 때마다 교체하여 사용하는 방식
- 기능을 점점 확장할 수록 한 클래스의 크기가 점점 비대해지고 코드의 연관성이 높아져 유지 보수 및 기능의 추가가 어려워진다.
- 각각의 동작 알고리즘을 개별적인 전략 클래스로 각각 추출합니다. 원래의 클래스에는 전략 클래스를 참조하여 저장하는 필드가 필요하다. 인터페이스를 통하여 전략 클래스를 작동 시킨다. 전략 클래스의 선택을 클라이언트가 한다.
- Template Method
- 상위 클래스에서 흐름을 제어하고 하위 클래스에서 동작을 처리하는 방식
- 비슷한 기능을 하는 클래스를 여러개 생성하면 유사한 코드가 중복 생성되고 처리 클래스를 결정하기 위해서 많은 조건문이 필요하다.
- 공통된 부분을 추출하여 인터페이스로 만들어 상속을 통하여 하위 클래스에서 동작의 세부 내용을 구현한다.
- Visitor
- 로직을 가지고 있는 객체와 로직을 적용 받을 객체를 분리하는 방식
- 새로운 동작을 추가하기 위해서는 클래스의 구조 변경이 필요하고 오류가 발생 할 수 있다.
- 새로운 동작을 기존 클래스에 통합하지 않고 visitor 클래스로 배치한다. 사용자가 사용할 visitor 객체를 선택하도록 한다.
- Interpreter
- 언어 문법이나 표현을 평가할 수 있는 방법을 제공한다.
728x90
'주제 정리' 카테고리의 다른 글
[아키텍처]마이크로 서비스 (0) | 2021.08.19 |
---|---|
[JAVA] HashSet (0) | 2021.08.13 |
[JAVA] HashTable, HashMap (0) | 2021.08.12 |
Object 객체 탐구 (0) | 2021.08.07 |
[JAVA] StringBuilder와 StringBuffer (0) | 2021.08.07 |