디자인 패턴 -생성 패턴(creational pattern)
회사 코드 리팩토링 을 위한 복습 및 정리
혹시라도 나와 같이 리팩토링 혹은 새 프로젝트의 작업을 어떻게 할지 고민하는 사람을 위해 간략하게나마 작성하였다.
일단 내가 찾고자 하는 것은 인스턴스 생성 보단 객체 생성 패턴이지만 이왕 다시 공부를 정리할겸 클래스 생성 패턴도 정리한다.
생성 패턴(creatinal pattern)
생성패턴은 인스턴스를 만드는 절차를 추상화하는 패턴으로 객체를 생성 합성하는 방법이나 객체의 표현 방법과 시스템을 분리해준다.
팩토리 메서드 패턴(Factory Method Pattern)
객체 생성을 위한 인터페이스는 정의하지만, 어떤 클래스의 인스턴스를 생성할지에 대한 결정은 서브클래스에 위임함으로써 객체 생성로직을 캡슐화하는 패턴으로, 생성할 객체 타입이 예측불가할 때 사용가능하다.
즉, new 연산자를 통해 객체 생성하는 것이 아닌 제품 객체들을 도맡아 생성하는 공장 클래스를 만들고 이를 상속하는 서브 공장 클래스의 메서드에서 여러가지 제품 객체 생성을 각자 책임 지는 것이다.
또한 객체 생성에 필요한 과정을 템플릿화 하여 객체 생성에 관한 전처리나 후처리를 통해 생성 과정을 다양하게 처리하여 객체를 유연하게 정할 수 있는 특징도 있다.
팩토리 메서드 패턴 구조
Creator: 최상위 공장 클래스로서, 팩토리 메서드를 추상화하여 서브클래스로 하여금 구현하도록 함.
객체 생성 처리 메서드(someOperration) : 객체 생성에 관한 전처리, 후처리를 템플릿화한 메서드
팩토리메서드(createProduct): 서브 공장 클래스에서 재정의할 객체 생성 추상 메서드
concreateCreator: 각 서브 공장 클래스들은 이에 맞는 제품 객체를 반환하도록 생성 추상 메서드를 재 정의한다. 즉 제품 객체 하나당 그에 걸맞는 생산 공장 객체가 위치된다.
Product: 제품 구현체를 추상화
ConcreateProdcut: 제품 구현체
팩토리 메소드 패턴은 객체를 만들어 내는 공장(Factory 객체)를 만드는 패턴이며 어떤 클래스의 인스턴스를 만들지는 미리 정의한 공장 서브 클래스에서 결정.(결합도를 낮추고 유지보수 용이성 올림)
최상위 공장 클래스는 반드시 추상 클래스로 선언XX (JAVA 8 이후 추가된 인터페이스의 디폴트 메서드를 통해 팩토리 메서드를 선언하면 되기 때문)
클래스 흐름
팩토리 메서드 패턴 장점과 단점
사용이 필요한 시기
클래스 생성과 사용의 처리 로직을 분리하여 결합도를 낮추고자 할때
코드가 동장해야 하는 객체의 유형과 종속성을 캡슐화를 통해 정보 은닉처리 하는 경우
라이브러비 혹은 프레임 워크 사용자에게 구성 요소를 확장하는 방법 제공하는 경우
기존 객체를 재구성하는 대신 기존 객체를 재송하여 리소스를 절약하고자 하는 경우
장점
생성자(Creator)와 구현 객체(concrete product)의 강한 결합을 피할 수 있다.
팩토리 메서드를 통해 객체의 생성 후 공통으로 할 일을 수행하도록 지정해줄 수 있다.
캡슐화, 추상화를 통해 생성되는 객체의 구체적인 타입을 감출 수 있다.
단일 책임 원칙 준수: 객체 생성 코드를 한 곳 (패키지, 클래스 등)으로 이동하여 코드를 유지보수하기 쉽게 할 수 있으므로 원칙을 만족
개방/폐쇄 원칙 준수: 기존 코드를 수정하지 않고 새로운 유형의 제품 인스턴스를 프로그램에 도입할 수 있어 원칙을 만족(확장성 있는 전체 프로젝트 구성이 가능)
생성에 대한 인터페이스 부분과 생성에 대한 구현 부분을 따로 나뉘었기 떄문에 패키지 분리하여 개별로 여러 개발자가 협어을 통해 개발
단점
각 구현체 마다 팩토리 객체들을 모두 구현해주어야 하기 떄문에, 구현체가 늘어날때 마다 팩토리 클래스가 증가하여 서브 클래스 수가 폭발한다.
코드의 복잡성이 증가한다.
추상 팩토리 패턴(Abstract Factory Pattern)
연관성 있는 객체군이 여러개 있을 경우 이들을 묶어 추상화
제품 '군' 집합을 타입 별로 찍어낼 수 있다는 점이 포인트 이다.
추상 팩토리 패턴 구조
AbstarctFactory: 최상위 공장 클래스, 여러개의 제품들을 생성하는 여러 메서드들을 추상화 한다.
ConcreateFactory:서브 공장 클래스들은 타입에 맞는 제품 객체를 반환하도록 메서드들을 재정의 한다.
AbstractProdcut:각 타입의 제품들을 추상화한 인터페이스
ConcreteProduct(Product A ~ ProdcutB): rㅏㄱ 타입의 제품 구현체들. 이들은 팩토리 객체로부터 생성된다.
Client: Client는 추상화된 인터페이스만을 이용하여 제품을 받기 떄문에, 구체적인 제품, 공장에 대해서는 모른다.
추상 팩토리 패턴 흐름
클래스 흐름
사용 시점
관련 제품의 다양한 제품군과 함께 작동해야 할때, 해당 제품의 구체적인 클래스에 의존하고 싶지 않은 경우
여루 제품군 중 하나를 선택해서 시스템을 설정해야하고 한 번 구성한 제품을 다른것으로 대체할 수도 있을 때
제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 노출하시키고 싶을 때
장점
객체를 생성하는 코드를 분리하여 클라이언트 코드와 결합도를 낮출 수 있음.
제품 군을 쉽게 대체 할 수 있다.
단일 책임 원칙 준수
개방/ 폐쇄 원칙 준수
단점
각 구현체마다 팩토리 객체들을 모두 구현해주어야 하기 때문에 각체가 늘어날때 마다 클래스가 증가하여 코드의 복잡성이 증가한다.(팩토리 패턴 공통적인 문제)
기존 추상 팩토리의 세부사항이 변경되면 모든 팩토리에 대한 수정이 필요해진다. 이는 추상 팩토리와 모든 서브클래스의 수정을 가져온다.
새로운 종류의 제품을 지원하는 것이 어렵다. 새로운 제품이 추가되면 팩토리 구현 로직 자체를 변경해야 한다.
새로운 종류의 제품을 지원하는것이 어렵다. 새로운 제품이 추가되면 팩토리 구현 로직 자체를 변경해야한다.
비슷하지만 다른 (Abstract Facory vs Factory Method)
팩토리 메서드 | 추상 팩토리 | |
공통점 | 객체 생성과정을 추상화한 인터페이스 제공 객체 생성을 캡슐화함으로써 구체적인 타입을 감추고 느슨한 결합 구조를 표방 |
|
차이점 | 구체적인 객체 생성과정을 하위 또는 구체적인 클래스로 옮기는 것이 목적 | 관련 있는 여러 객체를 구체적은 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적 |
한 Facotory당 한 종류의 객체 생성 지원 | 한 Factory에서 서로 연관된 여러 종류의 객체 생성을 지원 | |
메서드 레벨에서 포커스를 맞춤으로써, 클라이언트의 ConcreteProduct 인스턴스의 생성 및 구성에 대한 의존을 감소 | 클래스(Factory) 레벨에서 포커스를 맞춤으로써 클라이언트의 CocreateProdcut 인스턴스 군의 생성 및 구성에 대한 의존을 감소 |
빌더 패턴(Builder Pattern)
복잡한 객체의 생성과정과 표현방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴이다.
생성자에 들어갈 매개변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식
2가지 builder 디자인 패턴
이펙티브 자바의 빌더 패턴 : 생성시 지정해야 할 인자가 많을때 사용. 객체의 일관성과 불변성이 목적
GoF의 빌더 패턴: 객체의 생성 단계 순서를 결정해두고 각 단계를 다양하게 구현하고 싶을때 사용.
심플 빌더 패턴(effective java)
Gof 와 다르게 구분을 위해 위와 같이 부르기도 한다.
생성자가 많을 경우 혹은 변경 불가능 불변 객체가 필요한 경우 코드의 가독성과 일관성, 불변성을 유지하는 것에 중점을 둔다.
빌더 구현 시 정적 내부 클래스(statuc inner class) 로 구현
정적 inner 클래스로 구현되는 이유
빌더 클래스ㄹ는 하나의 대상 객체 생성만을 위해 사용
대상 객체는 오로지 빌더 객체에 의해 초기화 된다.
즉, 생성자를 외부에 노출시키면 안되기 때문에 생성자를 private 로 하고, 내부 빌더 클래스에서 priate 생성자를 호출함으로써 오로지 빌더 객체 의해 초기화 되도록 설계
inner class 를 쓰면 좋은건 알겠는데 왜 하필 static 으로 선언해주어야하나면, 정적 내부 클래스는 외부 클래스의 인스턴스없이도 생성 할 수 있는데 만일 일반 내부 클래스로 구성한다면 내부 클래스를 생성하기도 전에 외부 클래스를 인스턴스화 해야한다. 빌더가 최종적으로 인스턴스를 먼저 생성해야 한다면 무순이 생기기 떄문.
메모리 누수문제 때문에 statuc 으로 내부 클래스르 정의해주어야 한다.
디렉터 빌더 패턴(GOF)
Builder: 빌더 추상 클래스
ConcreteBuilder: Builder 의 구현체, Product생서을 담당
Director : Builder 에서 제공하는 메서드들을 상요해 정해진 순서대로 Product 생성하는 프로세스를 정의
Product: Director가 Builder 로 만들어낸 결과물
이러한 구조는 클라이언트가 직접 빌더의 모든 API를 사용하는게 아닌 Director를 통해서 간단하게 인스턴스를 얻어올 수 있고 코드를 재사용할 수 있도록 한다.
Lombok을 통한 빌더 구현
롬복 @Builder는 GOF 가 아닌 심플 빌더 패턴
빌더 어노테이션 구현
@Builder : PersonBuilder 빌더 클래스와 이를 반환하는 builder 메서드 생성
@AllAgrgsConstructor(access - AccessLevel.PRIVATE) : @Builder 어노테이션을 선언하면 전체 인자를 갖는 생성자를 자동으로 만드는데 이를 private 생성자로 연결
필수파라미터 빌더 구현
@Builder 이용시 필수 파라메터 지정 할 수 없다. 따라서 대상 객체안에 별도의 builder() 정적 메서드를 구현
// 필수 파라미터 빌더 메서드 구현
public static PersonBuilder builder(String name, String age) {
// 빌더의 파라미터 검증
if(name == null || age == null)
throw new IllegalArgumentException("필수 파라미터 누락");
// 필수 파라미터를 미리 빌드한 빌더 객체를 반환 (지연 빌더 원리)
return new PersonBuilder().name(name).age(age);
}
프로토타입 패턴(Prototype Pattern)
객체를 생성할때 기존 객체의 복사를 통해 새로운 객체를 생성하는 디자인 패턴
프로토 타입 패턴을 구현할 때는 복제를 위해 자바에서 제공하는 Cloneable 인터페이스를 구현하고, clone() 메서드를 오버라이드 하여 구현
장점
객체 생성비용과 시간을 줄임
새로운 객체를 생성 할 때 객체 생성 과정에서 발생 할 수 있는 오버 헤드를 줄일 수 있다.
객체 생성 방법이 복잡하거나 생성할 객체의 탕비이 동적으로 결정되는 경우 유용
단점
복제할 객체가 많은 경우 메모리 사용량이 늘어나고 객체생성 빙요을 줄이기 위해 객체의 상태를 공유하는 경우 부작용이 발생 할 수 있다.
싱글턴 패턴(Sigleton Pattern)
디자인 패턴중 가장 개념적으로 간단한 패턴.
해당 패턴은 단 하나의 유일한 객체를 만들 기위한 코드 패턴이다.
매모리 절약을 위해, 인스턴스가 필요할 때 똑같은 인스턴스를 새로 만들지 않고 기존의 인스턴스를 가져와 활용
리소스를 많이 차지하는 역할을 하는 무거운 클래스일때 적합하다.
싱글톤 패턴 구현 원리
생성자를 통해 인스턴스화 하는 것을 재현하기 위해 클래스 생성자 메서드에 private 키워드를 붙여주면 된다.
정적 메서드로 getInstance() 통해 객체를 불러와 변수에 저장하고 이를 출력해보면 똑같은 객체 주소를 가지고 있는걸 볼 수 있다.
즉 객체 하나만 생성하고 여러 변수에 불러와도 돌려쓰기를 한 것아다.
실글톤 패턴 구현 기법
Eager Initialization
Static block initialization
Lazy initialization
Thread safe initialzation
Double-Checked Locking
Bill pugh Solution
Enum 이용
싱글톤 의 문제점
모듈간 의존성이 높아진다.
SOLID 원칙에 위배되는 사례가 많다.
TDD 단위 테스트에 에로사항이 있음
객체 구현 패턴
정적 팩토리 메서드 패턴
개발자가 구성한 Stiatic Method를 통해 간접적으로 생성자를 호출하는 객체를 생성하는 디자인 패턴.
우리는 지금까지 객체를 인스턴스화 할때 직접적으로 생성자를 호출하여 생성
GOF 팩토리 메서더, 추상팩토리 패턴의 개념을 따와 심플하게 변형시킨 팩토리 변형 패턴
특징
생성 목적에 대한 이름 표현이 가능하다
인스턴스에 대해 통제 및 관리가 가능하다
하위 자료형 객체를 반환할 수 있다
인자에 따라 다른 객체를 반환하도록 분기할 수 있다
객체 생성을 캡슐화 할 수 있다
정적 팩토리 메서드 네이밍 규칙
from: 하나의 매개 변수를 받아서 객체를 생성
of:여러개의 매개 변수를 받아서 객체를 생성
getInstatince | instance: 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음
newInstance | create: 항상 새로운 인스턴스를 생성
get[Order Type]: 다른 타입의 인스턴스를 생성. 이전에 반환헀던 것과 같을 수 있음
new[Order Type]: 항상 다른 타입의 새로운 인스턴스를 생성
문제점
private 생성자일 경우 상속 불가능
API 문서에서의 불편함
점층적 생성자 패턴
생성자 등에 형태로 매개변수 개수만큼 생성자를 늘리는 방식
특징
사용자가 설정하길 원치 않는 매개변수까지 어쩔 수 없이 값을 지정해야 한다.
매개변수 조합에 따라 생성자 수가 쓸데없이 많이 늘어날 수 있다.
매개변수 수가 늘어나면 코드 작성 및 기독성이 저하된다.
(어떤 매개변수 값을입력하고 있는건지 개수는 제대로 입력한건지 계속 확인해야 하는불편함)
매개변수가 많아질수록 많은 조합이 만들어지고, 생성자의 수가 많아집니다. 이는 코드 작성 효율과 가독성이 저하되는 영향을 줍니다.
클래스의 생성자를 호출하는 입장에서 해당 매개변수가 맞는지, 매개변수의 개수는 제대로 입력한 것인지 확인해야 하는 불편함이 있습니다.
매개변수의 타입이 같은 경우 생성자를 만들 수 없습니다.
자바빈즈 패턴
매개변수가 없는 생성자로 객체를 만든 후 세터(setter) 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
장점
코드가 같긴하지만 인스턴스를 만들기 쉽다.
가독성이 좋다.
단점
객체 하나를 만들기 위해서 메서드를 여러개 호출해야 한다.
객체 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다. (점층적 생성자 패턴에서는 매개변수들이 유효한지를 생성자에서 환인하면 일관성 유지 가능)
클래스를 불면으로 만들 수 없다.(해결책: freeze 메서드 사용)
빌더 패턴(심플 빌더 패턴)
위 상세 참조
회사 현재 문제점
생성 패턴이 레거시의 경우 자바 빈즈 패턴
새롭게 이직해온 사람의 경우 점층적 생성자 패턴, 점층적 생성자 패턴 등 을 적용하여 사용하고 있다.
점청적 생성자 패턴의 경우 오버로딩이 파라메터에 따라 늘어나면서 가독성 및 효율이 떨어지고
자바 빈즈의 경우 일관성과 불변성에 문제가 있다
또 정적 팩토리 메서드 패턴 또한 파라메터가 늘어남에 따라 문제가 있다.
이에 나는 빌더 패턴을 적용을 할려고 생각중이다. 생성을 함에 있어서 일관성과 불변성을 유지하고 가독성을 생각하여 lobmok을 통해서 적용할려고 한다. 완벽한 생성 패턴은 없지만 외부 연동 및 내부 연동 시에도 계층형 구조를 가져가고 파라메터 수 또한 상당히 많다 가장 적합한 듯하다.