0. Overview
Optional 관련 강의를 듣다가, Optional.of() 와 같은 메서드를 정적 팩토리 메서드라고 듣게 되었습니다. "정적 팩토리 메서드"라는 키워드를 보고 정적 팩토리 메서드가 무엇인지 정리하고자 글을 작성하게 되었어요. 🫡
1. 정적 팩토리 메서드 ?
정적 팩토리 메서드는 객체의 생성을 담당하는 클래스 메서드라고 합니다. (사실 무슨 말인지 아직은 잘 모르겠어요..)
보통 자바를 공부할 때 객체를 생성하기 위해서는 new 키워드를 사용한다고 알고 있을 겁니다. 정적 팩토리 메서드는 new 키워드를 직접적으로 사용하지 않을 뿐, 클래스 내에 선언되어 있는 메서드를 내부의 new를 이용해 객체를 생성해 반환하는 것이라고 합니다.
즉, 정적 팩토리 메서드를 통해 new 를 간접적으로 사용하는 것이죠. 결국 "객체 생성 메서드"라고 정의할 수 있습니다.
정적 팩토리 메서드라는 용어가 생소하게 느껴질 수 있지만 (저도 그래요 ...), 사실 그리 생소하지 않은 방법이에요. 자바에서 말하는 인스턴스화 => 즉 , 객체 생성을 흔히 사용하는 생성자가 아닌 정적(static)메서드로 하는 것을 정적 팩토리 메서드라고 합니다.
1 - 1 Optional.of()
정적 팩토리 메서드의 대표적인 예시 하나가 바로 Java8 부터 추가된 Optional 입니다. 사실 우리는 정적 팩토리 메서드를 자주 사용하고 있었습니다. Optional은 정적 팩토리 메서드를 정석으로 보여주는 객체라고해요.
앞서, 정적 팩토리 메서드는 객체를 생성하는 메서드라고 정의했었는데 Optional은 "new" 키워드 대신 "of()" 메서드를 이용해 객체를 만들 수 있게 설계 되었습니다. 이제 메서드가 객체를 생성한다. 라는 개념이 무엇인지 조금씩 감이 잡히시죠? - (네 ㅋ)
그런데 여기서 중요한 것은, 왜 자바에서 정의한 "생성자"를 사용해 객체를 생성하는 것이 아닌, 메서드를 사용해 객체를 생성하느냐 입니다. 이제 알아보도록 합시다.
2. 네이밍 규칙
정적 팩토리 메서드를 쓰는 이유를 알아보기 전에 간단하게 정적 팩토리 메서드의 네이밍 규칙에 대해 살펴보겠습니다.
- from : 하나의 매개 변수를 받아 객체를 생성합니다.
- of : 여러 개의 매개 변수를 받아 객체를 생성합니다.
- getInstance | instance : 인스턴스 생성, 이전에 반환했던 것과 같을 수 있음
- newInstance | create : 항상 새로운 인스턴스 생성
- get[OrderType] : 다른 타입의 인스턴스 생성, 이전에 반환했던 것과 같을 수 있음
- new[OrderType] : 항상 다른 타입의 새로운 인스턴스를 생성
3. 정적 팩토리 메서드를 쓰는 이유
1. 생성 목적에 대한 이름 표현이 가능하다.
지금까지 우리는 클래스 설계시 다양한 타입의 객체를 생성하기 위해, 생성 목적에 따라 생성자를 오버로딩 해서 구분했어요.
ex) Person 클래스
- 이름, 나이, 성별이 있는 생성자
- 이름, 나이, 성별, 직업이 있는 생성자 등..
해당 생성자의 문제는 new 키워드를 통해 생성자로 생성하게 되면, 각각 목적에 맞는 의도를 파악하기가 어렵단 것입니다.
new Person(이름,나이) / new Person(이름,나이,직업) --> 둘의 의도를 파악하실 수 있겠나요 ?...
따라서 정적 팩토리 메서드는 각각 이름 표현을 통해 의도를 파악하기 용이합니다.
public static Person noJobPerson(String name, String age) { ... } public static Person yesJobPerson(String name, String age, String job) { ... }
2. 인스턴스에 대해 통제 및 관리가 가능하다.
1. 메서드를 통해 한 단계 거쳐 간접적으로 객체를 생성하기 때문에, 전반적인 객체 생성 및 통제 관리를 할 수 있게 되고 필요에 따라 항상 새로운 객체를 생성하거나, 하나만 만들어두고 이를 공유하여 재사용하게 하여 불필요하는 객체 생성을 방지할 수 있어요.
ex) Singleton 디자인 패턴 : getInstance() 라는 정적 팩토리 메서드로 하나의 객체만 반환 (메모리 절감)
2. 인스턴스에 대한 캐싱(Caching) 절차 구조를 정적 팩토리 메서드로 구현할 수 있습니다. 인스턴스에 대해 캐싱을 한다면 필요한 인스턴스만 뽑아 재사용하여 메모리를 절약할 수 있게 됩니다.
ex) HashMap 자료구조를 이용해 key 값을 통해 인스턴스를 캐싱하여 관리 (객체 존재 - 반환 , 객체 없음 - 생성)
이렇게 인스턴스를 통제하는 것은 인스턴스가 단 하나뿐임을 보장하는 것이고, Flyweight 디자인 패턴을 적용했다고 볼 수 있습니다.
💡 Flyweight 패턴 ?
플라이웨이트 패턴은 각 객체에 모든 데이터를 유지하는 대신 여러 객체들 간 상태의 공통 부분을 공유해서 사용할 수 있는 RAM에 더 많은 객체들을 포함할 수 있도록 하는 구조 디자인 패턴입니다.
3. 하위 자료형 객체를 반환할 수 있다.
클래스의 다형성의 특징을 응용한 특징입니다. 메서드 호출을 통해 얻을 객체의 인스턴스를 자유롭게 선택할 수 있는 유연성을 갖는 특징이에요.
interface Bag{} class Backpack implements Bag {} class CrossBag implements Bag {} class Bags { public static Bag getBackPack(){ return new BackPack(); } public static Bag getCrossBag(){ return new CrossBag(); } }
위와 같은 예시가 있습니다. Java 8 버전부터는 인터페이스가 static 메서드를 가질 수 있게 되었죠 ? 따라서 인터페이스에 그냥 정적 메서드를 선언하면 됩니다~
interface Bag{ public static Bag getBackPack(){ return new BackPack(); } public static Bag getCrossBag(){ return new CrossBag(); } }
4. 인자에 따라 다른 객체를 반환하도록 분기할 수 있다.
매개변수를 받고, 메서드 블록 내에서 분기문을 통해 여러 자식 타입의 인스턴스를 반환하도록 응용 구성이 가능합니다.
interface Bag{ public static Bag getBag(int price){ if(price > 10000) return new BackPack(); else return new CrossBag(); } }
5. 객체 생성을 캡슐화 할 수 있다.
생성자를 사용하는 경우 외부에 내부 구현을 드러내야 하는데, 정적 팩토리 메서드는 구현부를 외부로 부터 숨길 수 있어 캡슐화 및 정보 은닉을 할 수 있다는 특징이 있습니다. 동시에 구현체를 숨겨 의존성을 제거해주는 장점도 가져요!
4. 정적 팩토리 메서드 문제점
생성자보단 정적 팩토리 메서드를 고려하는게 좋지만, 단점도 존재하기 때문에 이를 잘 파악하는 것이 중요합니다.
1. private 생성자일 경우 상속 불가능
정적 팩토리 메서드로 클래스를 설계하면 생성자를 private 접근 제어자로 설정하게 되기 때문에 상속을 이용한 확장이 불가능해집니다.
2. API 문서에서의 불편함
생성자는 자바 언어에서 하나의 스펙이기 때문에 JavaDoc 같은 문서에서 쉽게 스펙을 찾아볼 수 있지만, 정적 팩토리 메서드는 개발자가 임의로 만든 메서드이기 때문에 찾아서 이해해야 한다는 점이 단점이라고 할 수 있다.
-> 이를 API 문서화와 네이밍 컨벤션을 통해 문제점을 극복한다.
< 참고 자료 >