tonykang22 / study

0 stars 0 forks source link

[Effective Java] 아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라. #31

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

아이템 1. 생성자 대신 정적 팩토리 메소드를 고려하라.

핵심 정리


장점 1. 이름을 가질 수 있다. (원하는 객체의 특징을 이름으로 더 잘 표현할 수 있다.)

Before


After

public class Order {

    private boolean prime;
    private boolean urgent;
    private Product product;

    public static Order primeOrder(Product product) {
        Order order = new Order();
        order.prime = true;
        order.product = product;
        return order;
    }

    public static Order urgentOrder(Product product) {
        Order order = new Order();
        order.urgent = true;
        order.product = product;
        return order;
    }


장점 2. 호출될 때마다 인스턴트를 새로 생성하지 않아도 된다.

예시


장점 3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

장점 5. 정적 팩토리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

예시

public class HelloServiceFactory {

public static void main(String[] args) {
    ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
    Optional<HelloService> helloServiceOptional = loader.findFirst();
    helloServiceOptional.ifPresent(h -> {
        System.out.println(h.hello());
    });

    // 동일 동작을 위해 구현체를 호출할 수도 있지만, 유연성이 그만큼 떨어지는 것이다.
    HelloService helloService = new KoreanHelloService();
    System.out.println(helloService.hello());

}



<br>

## 단점 1. 상속을 하려면 public이나 protected 생성하기 필요하기 때문에 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다. 
## 단점 2. 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
- 컬렉션 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다는 의미이다.
- Javadoc : Constructor는 별도의 컬럼이 있어서 눈에 쉽게 띈다. 정적 팩토리 메소드도 물론 Method의 컬럼에서 확인할 수 있지만, 메소드가 많아지면 많아질수록 가시성이 떨어지는데, 이 또한 단점이다.
    * 그마나 네이밍으로 (getInstance, newInstance, of, valueOf 등) 분류할 수 있기는 하다.
    * Javadoc 으로 잘 문서화 해둔다면 그래도 이 단점을 어느정도 극복할 수 있다.

<br>

## 아이템 1. 생성자 대신 정적 팩토리 메소드를 고려하라.
## 완전 공략
- **열거 타입**은 인스턴트가 하나만 만들어짐을 보장한다.
- 같은 객체가 자주 요청되는 상황이라면 **플라이웨이트 패턴**을 사용할 수 있다.
- 자바 8부터는 **인터페이스가 정적 메소드**를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 필요가 없다.
- **서비스 제공자 프레임워크**를 만드는 근간이 된다.
- **서비스 제공자 인터페이스**가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.
- 브리지 패턴
- 의존 객체 주입 프레임워크 (E.g. Spring framework)

<br>

## 완전 공략 1. 열거 타입 (Enumeration)
- 상수 목록을 담을 수 있는 데이터 타입.
- 특정한 변수가 가질 수 있는 값을 제한할 수 있다. **타입-세이프티 (Type-Safety)**를 보장할 수 있다.
    * Enum으로 표현할 값을 String, char, int 등으로 표현했을 경우, 의도하거나 정의하지 않은 값이 주입될 가능성이 있다.
    * 이를 방지하거나 검증하기 위한 코드를 추가적으로 작성해야 한다.
    * Enum 값들은 JVM 내에서 오로지 딱 1 개만 만들어진다. 
    * (Enum을 사용하는 방법이 싱글톤 패턴을 안전하게 구현할 수 있는 방법 중 하나인 이유이다.)
- 질문1) 특정 enum 타입이 가질 수 있는 모든 값을 순회하며 출력하라. 
- 질문2) enum은 자바의 클래스처럼 생성자, 메소드, 필드를 가질 수 있는가? 
- 질문3) enum의 값은 == 연산자로 동일성을 비교할 수 있는가?
- 과제) enum을 key로 사용하는 Map을 정의하세요. 또는 enum을 담고 있는 Set을 만들어 보세요.

<br>

- 답1) Enum.values() 에 Enum의 값들이 다 들어있다. 
    * (Arrays.stream(OrderStatus.values()).forEach(System.out::println);
- 답2) 된다.
- 답3) 된다. ('만약 00 상태라면 00 해라'의 코드에서 .equals를 쓰는 것보다 낫다. (null safe하기 때문이다.)
- 과제 답) EnumMap, EnumSet을 사용하는 것이 다른 Map, Set을 사용하는 것보다 훨씬 빠르다.
    * EnumMap, EnumSet도 Java Collections Framework의 멤버이다.
    * Hash는 물론, 다른 어떤 방식보다도 검색에 특화되어 있지만 해싱이라는 과정이 필요하기 마련이다.
    * 그러나 enum은 단일 객체를 보장하기 때문에 이러한 과정이 필요하지 않다.
    * key 값으로 null을 받지 않고 NullPointerException을 던진다.
    * EnumMap은 (Enum에 명시되어 있는) 순서 또한 보장된다. (탐색도 hashmap보다 더 빠르고 treemap의 장점도 가졌다.)

<br>

## 완전 공략 2. 플라이웨이트 패턴
- 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴이다.
    * '같은 객체가 자주 사용된다' - 매번 새로운 객체를 만드는 것이 아니라 같은 객체를 캐싱해두었다가 사용하는게 좋지 않을까.
- 자주 변하는 속성(또는 외적인 속성, extrinsit)과 변하지 않는 속성(또는 내적인 속 성, intrinsit)을 분리하고 재사용하여 메모리 사용을 줄일 수 있다.

<br>

## 완전 공략 3. 인터페이스와 정적 메소드 (자바8과 9에서 주요 인터페이스 변화)
- 기본 메소드 (default method)와 정적 메소드를 가질 수 있다.
- 기본 메소드
    * 인터페이스에서 메소드 선언 뿐 아니라, 기본적인 구현체까지 제공할 수 있다.
    * 기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다. 
- 정적 메소드
    * 자바 9부터 private static 메소드도 가질 수 있다. (내부의 다른 public static한 메소드들에서 사용할)
    * 단, private 필드는 아직도 선언할 수 없다.
- 질문1) 내림차순으로 정렬하는 Comparator를 만들고 List<Integer>를 정렬하라. 
- 질문2) 질문1에서 만든 Comparator를 사용해서 오름차순으로 정렬하라.

<br>

- 답1) `Comparator<Integer> descendingOrder = (o1, o2) -> o2 - o1;`
- 답2) `descendingOrder.reversed()`
    * Comparator의 default 메소드로 추가되었다.

<br>

## 완전 공략 4. 서비스 제공자 프레임워크 (확장 가능한 애플리케이션을 만드는 방법)
- 구현 형태보다 개념(목적)이 중요하다. 
    * 개념(목적) - 확장 가능한 애플리케이션을 제공하는 것
    * 확장 가능한 애플리케이션 - 코드를 변경하지 않고 외적인 것을 변경했을 때 애플리케이션이 다르게 동작할 수 있게 하는 것.
- 주요 구성 요소
    * 서비스 제공자 인터페이스(SPI)와 서비스 제공자(서비스 구현체)
        * 인터페이스와 구현체가 따로 있어도 된다.
    * 서비스 제공자 등록 API(서비스 인터페이스의 구현체를 등록하는 방법)
        * `META-INF.services` 아래의 항목
    * 서비스 접근 API(서비스의 클라이언트가 서비스 인터페이스의 인스턴스를 가져올 때 사용하는 API)
        * `HelloService helloservice = applicationContext.getBean(HelloService.class);`
        * `ServiceLoader.load()` - 스프링을 쓰지 않는다면 좋은 대안이다.
- 다양한 변형
    * 브릿지 패턴
        * 목적 - 구체적인 것과 추상적인 것을 분리. (그 사이에 다리를 두는 형식의 패턴)
        * 서로 영향은 주지 않으면서 각각이 계층 구조로 발전할 수 있게 해준다.
        * e.g. 챔피온 - 스킨 (브릿지 패턴을 사용하지 않으면, 스킨이 하나 생기면 그에 상응하는 챔피온이 그 만큼 늘어나게 된다.)
        * PSA(Portable Service Abstraction) 까지도 연결이 된다.
    * 의존 객체 주입 프레임워크 
    * java.util.ServiceLoader
    * https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html
    * https://docs.oracle.com/javase/tutorial/ext/basics/spi.html

<br>

## 완전 공략 5. 리플렉션 (Reflection)
- 클래스로더를 통해 읽어온 클래스 정보(=거울에 반사된 정보)를 사용하는 기술
- 리플렉션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메소드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다.
- 언제 사용할까?
    * 특정 애노테이션이 붙어있는 필드 또는 메소드 읽어오기 (JUnit, Spring)
    * 특정 이름 패턴에 해당하는 메소드 목록 가져와 호출하기 (getter, setter)
        * Class.forName(), getConstructor(), newInstance(), getDeclaredMethod(), getDeclaredField() 등으로 참조 및 변경할 수 있다. (private 하더라도 가능하다.)
    * ...
- https://docs.oracle.com/javase/tutorial/reflect/