인스턴스를 생성하는 방법은 (1) public 생성자 (2) 정적 팩터리 메서드가 있다.
여기서 정적 팩터리 메서드를 사용할 때 찾을 수 있는 장점은 다음과 같다.
1. 이름을 가질 수 있다.
생성자는 클래스명(parameters) 형태로만 구현할 수 있지만 정적팩터리 메서드는 자신의 이름을 가질 수 있다. 하지만 자주 사용되는 직관적인 정적팩터리 메서드 이름이 있으니 참고하여 구현하도록 해야한다.(포스팅 하단에 정리되어있다.)
/*(1) valueOf 라는 메서드 명을 통해 반환되는 값을 유추할 수 있다.
true라는 값이 Boolean 형태로 반환되어 나오겠다는 사실을 알 수 있다. */
Boolean boolean1 = Boolean.valueOf(true);
/* (2) 생성자는 시그니처 중복이 불가하다. -> 같은 매개변수 타입, 갯수르 가지는 생성자는 여러개 작성할 수 없다.
한 클래스에 시그니처가 중복되는 생성자가 여러개 반복될 것 같으면 정적 펙터리 메서드 사용을 고려해보자*/
Boolean.logicalOr(boolean a, boolean b);
Boolean.logicalAnd(boolean a, boolean b);
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. - static 자원은 JVM 클래스로더의 "초기화" 부분에서 할당된다.
Foo foo = new Foo().valueOf(); (x)
Boolean boolean1 = Boolean.valueOf(); (o)
관련되어 나온 개념은
불변클래스(immutable Class)를 만들 수 있음
Boolean.valueOf() -> Boolean을 반환하지만 객체를 생성하지 않음. 한번 생성되면 그 값을 변경할 수 없다.
테스트코드에서 알 수 있다시피 true를 지칭하는 Boolean 타입의 변수는 같은 레퍼런스를 바라보고 있다. 하지만 new 를 통해 생성하는 인스턴스는 새로운 인스턴스이기 때문에 같은 레퍼런스를 바라보고 있지 않다.
플라이웨이트 패턴
데이터를 공유하여 메모리를 절약하는 패턴으로 공통으로 사용되는 객체는 한 번만 생성되고 공유를 통해 풀(Pool)에 의해 관리, 사용된다. 없으면 만들고 있으면 있는거 주는 패턴.
3. 반환타입의 하위 타입 객체를 반환할 수 있다.
4. 매개변수에 따라 다른 클래스 객체를 반환할 수 있다.(하위 타입의 조건만 만족한다면.)
3, 4 는 이어지는 장점이다. 아래 예제는 반환타입이 Ticket 이지만 static factory method에서 그 하위타입을 반환할 수 있음을 보여주는 예제이다. 정적팩터리 메서드를 사용하면 동적으로 적절한 타입의 객체를 반환할 수 있다.
public class Ticket {
public static Ticket getTicketByType(String type) {
if (type.equals("vip")) {
return VipTicket.INSTANCE;
}
if(type.equals("general")){
return GeneralTicket.INSTANCE;
}
new IllegalArgumentException("잘못된 타입입니다!");
}
}
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
- 반환값이 인터페이스여도 된다.
- 정적팩터리 메서드의 변경 없이 구현체를 바꿔끼울 수 있다 -> 반환 값의 구현체이기만 한다면 가능함. 이를 통해 유연한 시스템 구현이 가능하다.
import java.util.ArrayList;
import java.util.List;
public class TicketStore {
/** TicketSeller는 인터페이스이고 구현체가 없음에도 아래와 같은 메서드 작성이 가능하다.**/
public static List<TicketSeller> getSellers(){
return new ArrayList<>();
}
}
정적팩터리메서드의 활용 예시 - 서비스 제공자 프레임워크
- 서비스 인터페이스: 구현체의 동작 정의.
- 제공자 등록 API: 제공자가 구현체 등록
- 서비스 접근 API: 클라이언트가 서비스의 인스턴스 얻을 때 -> 원하는 구현체의 조건을 입력하고 그에 따라 기본 구현체를 반환하거나 조건에 부합하는 구현체를 반환할 수 있음. 이것을 정적 팩터리로 작성할 수 있다.
- 서비스 제공자 인터페이스(SPI): 서비스 인터페이스 인스턴스를 생성하는 팩터리 객체 설명
"보통의 API들은 구현체의 Interface를 외부로 공개하여 구현체를 사용하는 주체가 자신의 환경에 맞게 사용한다. 반면에 SPI는 사용자가 구현해야 할 Interface를 정의한다. SPI 사용자(보통은 driver vendor)가 자신의 환경에 맞는 구현체를 직접 정의하여 제공하면 SPI를 제공해준 service에서는 제공 받은 구현체를 불러다 사용하는 형태로 동작한다."
위의 개념을 JDBC - mysql로 정리한 내용.
Driver driver = new Driver() 는 jdbc 정의 인터페이스가 아닌 mysql 에서 제공하는 구현체임에 주의한다. Class.forName 으로도 등록이 가능하다.
위의 분류에 따르면 서비스 인터페이스는 Connection, 제공자 등록 API 는 DriverManager 의 registerDriver 메서드, 서비스 접근 API 는 DriverManager의 getConnection 메서드. 서비스 제공자 인터페이스는 sql 의 Driver 메서드이고 코드의 Driver 는 이 SPI 를 구현한 Driver 이다. 즉, MySQL 이 서비스 제공자가 된다.
참고하기 좋았던 링크
- https://devyongsik.tistory.com/294-
- https://itnext.io/java-service-provider-interface-understanding-it-via-code-30e1dd45a091
+ 추가할 내용) 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야한다
단점은,
1. 상속을 하려면 public이나 protected 생성자가 필요하기 때문에 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없음
- Collections 는 상속할 수 없다. 생성자가 private으로 구현되어있다.
- 상속보다 컴포지션을 유도할 수 있기 때문에 OCP 에 적합하다. 장점이 될 수도 있다.
2. 찾기가 어려움. 통용되는 네이밍 준수 필요
- from: 매개변수를 받아서 해당 타입의 인스턴스 반환. 형변환.
- of: 여러 매개변수를 받아 적합한 인스턴스 반환
- valueOf: from, of 보다 자세한 버전
- instance, getInstance: 매개변수 인스턴스를 반환하지만 보장하지는 않음
- create, newInstance: 매번 새로운 인스턴스 생성해 반환
- getType: 반환 타입과 팩터리메서드 클래스가 다름. Type은 반환 타입 명시
ex) FileStore fs = Files.getFileStore(path)
- type: getType, newType 간결한 버전
ex) Collections.list(legacyLitany)
'Reading Record > 이펙티브자바' 카테고리의 다른 글
아이템 [7] - 다 쓴 객체 참조를 해제하라 (0) | 2020.02.08 |
---|---|
아이템 [3] - private 생성자나 열거타입으로 싱글턴임을 보증하라 (0) | 2020.02.08 |
아이템[14] - Comparable을 구현할지 고려하라 (0) | 2020.02.07 |
아이템[11] - equals를 재정의 하려거든 hashCode도 재정의하라 (2) | 2020.01.26 |
아이템 [10] - equals는 일반 규약을 지켜 재정의하라 (0) | 2020.01.26 |