-
Static Factory MethodCS/Java (with Effective) 2022. 1. 13. 23:56
이전에 진행한 MSA 프로젝트를 좀 더..
사실 죽어가는 녀석 살려보고자Webflux / WebClient 학습을 진행하고 있습니다. 공부하다 보니 Webflux 공식 문서에서 읽다보니 아래와 같은 문구를 발견할 수 있었습니다.The simplest way to create a WebClient is through one of the static factory methods
WebClient를 가장 단순하게 생성하는 방법은 정적 팩토리 메소드를 이용하는 것이다. 라네요.
정적 팩토리 메서드? 무슨 디자인패턴 공부할 때 본 것 같기도 한 이 네이밍.. 사실 전 구면입니다. 후후
바로 이펙티브 자바 1장에서 만났습니다.
생성자 대신 정적 팩터리 메서드를 고려하라.
이 내용을 너무 건성으로 봤었는데, 상당히 괜찮은 방법이라 느껴져 공유해볼까 합니다.
아.. 사실 WebClient 먼저 빡세게 하고 정리하려했는데... 매혹 당했습니다!!
생성자는 모르시는 분.. 안 계시겠죠? 물론 모를 수도 있으니 조금만 검색해 보세요. ㅎㅎ
우리가 객체를 선언하고 해당 클래스의 인스턴스를 생성할 때 사용하는 것이 생성자입니다.
아래 코드에서 접근 한정자를 public으로 해둔 저 녀석이 생성자죠.
class Student { String name; int age; int classNo; public Student(String name, int age, int classNo) { this.name = name; this.age = age; this.classNo = classNo; } }
즉, 이 생성자로 인스턴스를 생성해 사용합니다. 의존성 주입을 하는 방식 중에도 이 생성자를 사용하는 방식이 있죠.
다음에 기회가 되면 이 방식에 대한 것도 정리해보도록 하겠습니다.
어쨌든 이렇게 무의식적으로 사용하던 생성자가 아닌 정적 팩토리 메서드를 고려하라니.. 처음엔 잘 납득이 되지 않겠죠.
Static Factory Method 이 친구.. 대체 뭐길래.. 그리고, 어떤 장점이 있는지 확인해보고 납득해 봅시다!
Static Factory Method ?
정적 팩토리 메서드는 객체의 인스턴스 생성을 맡은 클래스 메서드라고 정의할 수 있습니다.
여기서 Factory의 의미가 조금 모호한데, 디자인 패턴에 팩토리 메서드 패턴에 대해 참고해보세요.
Factory Method pattern
어떤 클래스의 인스턴스를 생성할 지에 대한 결정을 서브클래스가 내리도록 할 때
Factory => 객체를 생성하는 역할을 따로 분리
정적 팩토리 메서드 장점
- 이름을 가질 수 있다.
- 호출시 매번 인스턴스를 새로 생성할 필요 없다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력을 갖는다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
1. 이름을 가질 수 있다.
생성자로 인스턴스를 생성하는 경우 생성자 이름과 포함된 매개 변수만으로는 해당 객체의 특성을 제대로 설명하긴 어렵다.
위의 코드만 봐도 알 수 있죠. 물론 저렇게 단순한 객체는 학생의 생성자구나 할 수 있겠지만, 어떤 용도로 생성되는 객체의 인스턴스일지 상세한 설명과 그 가독성은 보장해줄 수 없습니다.
class AutoMoblieFactory { private static final int PASSENGERS = 5; private static List<CarType> carList; public static Car createType1NormalCar() { return new Car(carList.stream() .filter(!IS_AUTO_STICK) .limit(PASSENGERS) .collect(Collectors.toList())); } public static Car createType2AutoCar() { return new Car(carList.stream() .filter(IS_AUTO_STICK) .limit(PASSENGERS) .collect(Collectors.toList())); } }
생성자가아닌 정적 팩토리 메서드를 사용하면, 이렇게 객체 인스턴스를 반환하며, 어떤 용도인지 표현이 가능합니다.
위의 예제는 1종 보통 차량 또는 2종 자동으로 나누었습니다.
2. 호출시 매번 인스턴스를 새로 생성할 필요 없다.
문자열을 String pool에 생성하고 가져다 쓰듯이, 미리 설정해 생성해두고 참조해 쓰면 매번 새로 생성할 필요가 없습니다.
class StudentRecord { private static final int FIRST = 1; private static final int LAST = 30; private static final Map<Integer, StudentRecord> EXAM_RESULT = new HashMap<>(); static { IntStream.range(FIRST, LAST) .forEach(studentNumber -> EXAM_RESULT.put(studentNumber, new StudentRecord(studentNumber, Score.get(studentNumber)))); } private int number; private int score; private StudentRecord(int number, int score) { this.number = number; this.score = score; } public StudentRecord from(int number) { // static factory method return EXAM_RESULT.get(number); } }
위와 같이 생성해 맵에 저장해두고 참조를 통해 가져다 쓸 수 있습니다. 인스턴스를 생성해 캐싱해 재활용하는 방식이죠.
또한, 기존 생성자는 private 설정으로 외부에서 접근할 수 없도록 제한하고, Static Factory Method로 생성함으로써 생성해둔 학생 번호 이외의 정보로 생성되는 학생 데이터를 막을 수 있습니다.
Flyweight pattern 또한 이와 유사한 방식으로 보시면 되겠습니다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
class Algorithm { private static final String LOG_N = "O(logN)"; private static final String N_POW2 = "O(N^2)"; private static final String V_LOG_E = "O(VlogE)"; private static final String N = "O(N)"; public static Algorithm from(String timeComplexity) { if(timeComplexity.equals(LOG_N)) return new BinarySearch(); else if(timeComplexity.equals(N_POW2)) return new BFS(); else if(timeComplexity.equals(V_LOG_E)) return new Dijkstra(); else return new SlidingWindow(); } } class BinarySearch extends Algorithm { String timeComplexity = "O(logN)"; } class BFS extends Algorithm { String timeComplexity = "O(N^2)"; } class Dijkstra extends Algorithm { String timeComplexity = "O(VlogE)"; } class SlidingWindow extends Algorithm { String timeComplexity = "O(N)"; }
상속하고 있는 클래스의 정적 팩토리 메서드에서 하위 클래스 타입을 반환해도 위와 같이 에러없이 사용할 수 있습니다. 또한, 파라미터에 따라 'Algorithm'을 상속받은 서로 다른 하위 클래스 반환이 가능합니다.
이러한 유연성을 이용하면, 굳이 구현 클래스를 공개하지 않아도 해당 객체 반환이 가능해 API를 경량으로 유지할 수 있습니다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
이는 JDBC와 같은 서비스 제공자 프레임워크를 만드는 근간이됩니다. 서비스 제공자 프레임워크의 핵심 컴포넌트 중 하나인 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API가 유연한 정적 팩토리입니다.
정적 팩토리 메서드 단점
- 정적 팩토리 메서드만 제공하면 상속을 통한 하위 클래스 생성 불가능 (public or protected 생성자 필요)
- 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
정적 팩토리 메서드를 생성시 네이밍은 어떻게 약속되는지 확인하고 마무리 하겠습니다.
정적 팩토리 메서드 네이밍 컨벤션
- from : 하나의 매개 변수 => 객체 생성
- of : 여러개의 매개 변수 => 객체 생성
- getInstance | instance : 인스턴스 생성 (이전에 반환했던 것과 같을 수 있음)
- newInstance | create : 새로운 인스턴스 생성
- get[OtherType] : 다른 타입의 인스턴스 생성 (이전에 반환했던 것과 같을 수 있음)
- new[OtherType] : 다른 타입의 새로운 인스턴스 생성
결론적으로 코드의 유연성과 가독성, 안정성 등을 챙길 수 있는 방식이었습니다. 비록 단점은 존재하지만, 장점을 고려해 써보시는 것도 좋을 것 같습니다.
참고자료
Web on Reactive Stack - WebClient
Effective Java - 조슈아 블로크
반응형'CS > Java (with Effective)' 카테고리의 다른 글
Generic과 raw-type 그리고 비검사 경고 (0) 2022.04.28 Composition vs extends (0) 2022.03.02 HashCode (0) 2022.02.27 Singleton (0) 2022.02.08 Builder (0) 2022.02.06