Java
[Java] 정적 팩토리 메서드 (Static Factory Method)
도전하는 린치핀
2024. 7. 15. 22:39
1. 정적 팩토리 메서드 (Static Factory Method)란?
- 정적 팩토리 메소드는 개발자가 구성한 Static Method를 통해 간접적으로 생성자를 호출하여 객체를 생성하는 패턴
- 보통 객체를 인스턴스화 할 때, 생성자를 호출하며 객체를 생성하는데 생성자에는 다양한 단점이 존재할 수 있다.
- 이를 해결하기 위해, 객체 생성을 담당하는 메소드를 정적 팩토리 메서드라고 한다.
- 정적 팩토리 메서드는 이름에서 알 수 있듯이 GOF의 팩토리 메서드, 추상 팩토리 패턴에서의 팩토리 개념을 따온 것이라고 생각할 수 있다.
자바에서 '생성자 대신 정적 팩토리 메서드를 고려하라' 라는 말이 있을 정도로 정적 팩토리 메서드는 객체를 생성할 때, 꽤나 중요해 보인다.
생성자를 왜 사용하지 말고 더 나아가 정적 팩토리 메서드를 통해 객체를 생성하는지 의문이 들었다.
하지만 실제 정적 팩토리 메서드의 경우 단순히 생성자의 역할을 대신하는 것이 아닌 코드의 가독성을 향상시키고 객체 지향적 프로그래밍에 더욱 가까워질 수 있는 방법이다.
2. 정적 팩토리 메서드 (Static Factory Method) 장단점
2-1. 장점
장점 1. 이름을 가질 수 있어 가독성이 좋다.
- 기존 생성자를 통한 객체 생성에는 생성 목적에 따른 생성자 오버로딩을 통해 구분하여 객체를 생성하였다.
- 하지만 이것은 객체를 생성할 때, 개발자가 생성자의 매개변수 순서, 내부 구조를 정확히 알아야 목적에 맞는 객체를 생성할 수 있다는 문제가 발생한다.
- 즉, 생성자를 통해 객체를 설계할 때는 매개변수만으로 반환될 객체의 특정을 제대로 표현하기 어렵다.
- 이것은 객체 지향적인 측면과 코드의 가독성과 효율성 측면에서 매우 큰 단점이 될 수 있다.
- 하지만 정적 팩토리 메서드는 적절한 메서드 이름을 통해 객체의 생성 목적과 객체의 특성을 담아 낼 수 있다.
- 생성자 대신 정적 팩토리 메서드를 호출함으로써 생성될 객체의 특성에 대해 쉽게 묘사할 수 있다는 장점이 있어 코드의 가독성을 높여주게 된다.
- 더 나아가 정적 팩토리 메서드를 구성할 때, private 생성자를 두어 외부에서 new 키워드로는 접근할 수 없지만 객체 내부에서 생성자를 통해 객체를 생성하게 하는 방법도 있다.
장점 2. 호출 할 때마다 새로운 객체를 생성할 필요가 없다.
- 메서드를 통해 한단계 거쳐 간접적으로 객체를 생성하기 때문에, 기본적으로 전반적인 객체 생성 및 통제 관리를 할 수 있게 된다.
- 즉, 필요에 따라 항상 새로운 객체를 생성해서 반환할 수도 있고, 아니면 객체 하나만 만들어두고 이를 공유하여 재사용하게 하여 불필요한 객체를 생성하는 것을 방지 할 수 있는 것이다.
- 대표적인 예로 Singleton 디자인 패턴을 통한 하나의 객체만 반환하도록 하여 객체를 재사용하여 메모리를 아끼는 방법이 있다.
- 또다른 예로는 인스턴스에 대한 캐싱(Caching) 절차 구조를 정적 팩토리 메서드로 구현할 수 있다. 인스턴스에 대해 캐싱을 한다면 필요한 인스턴스만 뽑아 재사용하여 메모리를 절약할 수 있게 된다.
장점 3. 하위 자료형 객체를 반환할 수 있다
- 클래스의 다형성의 특징을 응용한 정적 팩토리 메서드 특징이다.
- 즉, 메서드 호출을 통해 얻을 객체의 인스턴스를 자유롭게 선택할수 있는 유연성을 갖는 장점이 생긴다.
- 이것은 인터페이스 기반의 프레임워크를 사용할 수 있게 해준다.
- 대표적으로 자바의 컬렉션 프레임워크인 java.util.Collections 클래스를 들 수 있는데, 이 클래스는 Collection 인터페이스를 반환하는 여러 정적 팩토리 메서드를 가지고 있다.
장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 메서드 블록 내에서 분기문을 통해 여러 자식 타입의 인스턴스를 반환하도록 응용 구성이 가능하다
- 위의 3번에서 설명한 하위 자료형 객체를 반환하는 것과 비슷한 개념이다.
- 만약 특정 인터페이스를 상속 받은 구현체가 있을 때, 객체 생성 시 상황에 따라 유동적으로 해당하는 구현체 타입으로 반환할 수 있다.
장점 5. 객체 생성을 캡슐화할 수 있다.
- DTO와 Entity간에는 자유롭게 형 변환이 가능해야 하는데, 정적 팩터리 메서드를 사용하면 내부 구현을 모르더라도 쉽게 변환할 수 있다.
- 만약 정적 팩토리 메서드를 쓰지 않고 DTO로 변환한다면 외부에서 생성자의 내부 구현을 모두 드러낸 채 해야할 것이다.
2-2. 단점
단점 1. private 생성자일 경우 상속 불가능
- 정적 팩토리 메서드는 생성자로 인스턴스를 생성하는 것을 막기 위해 생성자 접근 제어자를 private로 설정한다.
- 이때, 생성자가 private이기 때문에 해당 생성자에 접근할수가 없어 상속이 불가능하다는 문제가 생긴다.
- "상속 보단 합성" 원칙을 보면 상속에 대해 단점과 한계에 대해 많이 설명하고 있고, 상속보다는 합성을 사용하는 것을 권장하기 때문에, 첫번째 단점은 무조건적인 단점이라고 할 수 없다.
단점 2. 정적 팩토리 메서드를 다른 개발자들이 찾기 어렵다.
- 개발자가 임의로 만든 정적 메서드이기 때문에 다른 개발자가 사용 시 찾아 사용하기 어려울 수 있다.
- 하지만 이 단점도 아래 소개할 정적 팩토리 메서드 네이밍 컨벤션을 따르며 API 문서를 잘 작성한다면 어느정도 해결 할 수 있는 부분이다.
3. 정적 팩토리 메서드 네이밍 컨벤션
- from : 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
- valueOf : from과 Of의 더 자세한 버전
- instance | getInstance : 매개변수로 명시한 인스턴스를 반환하며, 이전 인스턴스와 같은 인스턴스임을 보장 하지 않음
- create | newInstance : instance | getInstance 와 같으나 항상 새로운 인스턴스를 생성해 반환 함을 보장함
- get[OrderType] : 다른 타입의 인스턴스를 생성하여 이전 인스턴스와 같은 인스턴스임을 보장하지 않음
- new[OrderType] : get[OrderType]과 같이 다른 타입을 반환하지만 항상 새로운 인스턴스임을 보장
예시 코드를 통해 확인해보자.
// from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
Date d = Date.from(instant);
// of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
// valueOf : from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
// instance 혹은 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
StackWalker luke = StackWalker.getInstance(options);
Calendar instance = Calendar.getInstance();
// create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
// getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. Type은 팩터리 메서드가 반환할 객체의 타입이다.
FileStore fs = Files.getFileStore(path);
// newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. Type은 팩터리 메서드가 반환할 객체의 타입이다.
BufferedReader br = Files.newBufferdReader(path);