본문 바로가기
책(독서)

[이펙티브자바] ~27p item1~4

by DevJR 2020. 10. 5.

아이템 1 - 생성자 대신 정적 팩터리 메서드를 고려하라

 

ex ) 책 예시 내용

public static Boolean valueOf(boolean b) {
   return b ? Boolean.TRUE : Boolean.FALSE;
}

생성자를 사용하지 않고 위와 같이 정적 팩터리 메서드를 사용하는게 좋다는 내용.

 

생성자 보다 정적 팩터리 메서드가 가지는 장점

1. 이름을 가질 수 있다.

아래부터의 예시는 이해한 바대로 만든 것임

new Animal(alive)

이렇게 생성자를 사용하면 '동물' 인 것은 알 수 있으나 단순히 '동물' 임을 알 수 있는데에 반해 '포유류' / '파충류' 등을 구분하고자 할 때에는 다음과 같이 정적 팩터리 메서드로 표현할 수 있다. (굳이 생성자를 통해 구분하는 방식인 [매개변수의 순서나 그런 것들을 지정해서 기억해두어야 하는] 그런 번거로움 없이 메서드 명으로 쉽게 파악이 가능하다)

Animal.mammal(alive) //포유류
Animal.reptile(alive) //파충류

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

-> 싱글턴 패턴도 일종의 팩터리 메서드이다. 이를 통해 한번 생성된 객체를 가져오게끔 처리하면 된다.

 

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

-> 메서드는 타입을 지정할 수 있는데 이를 인터페이스로 해두면, 해당 인터페이스를 구현하는 하위 타입 객체들은 다 반환할 수 있다는 소리이다. (생성자로는 생성하는 객체만 반환할 수 있기에 이게 불가능하다. -> 유연성이 떨어진다는 의미)

 

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

-> 위의 3번과 같은 맥락인데 반환 타입의 하위타입이면 어떤 클래스의 객체를 반환하는 관계가 없다. 이에 따라, 들어오는 매개변수에 따라 정적 팩터리 메서드 내부에서는 하위의 다른 클래스를 반환하게끔 처리할 수 있다. 

(정적 팩터리 메서드로 이루어진 API를 사용하는 클라이언트 측에서는 내부처리까지 알 방법도 없고, 알 필요도 없다.)

 

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

-> 계속해서 같은 얘기일 수 있는데, 반환 타입이 인터페이스일 경우 해당 인터페이스를 구현한 클래스는 10개일 수도 있고, 100개일 수도 있다. 혹은 아예 없을 수도 있다. 이 장점의 예시로는 JDBC가 있다.

DriverManager.getConnection 등으로 서비스 접근을 할 수 있는데, 구현에 따라 오라클 연결을 가져올 수도 있고 Mysql 연결을 가져올 수도 있다. JDBC 인터페이스를 각 업체에서 구현한 라이브러리(실제로는 이 부분도 아마 인터페이스일 것 같다. 실제 구현은 각자 제공업체 내부에 구현해두었겠지...)를 가져다 쓸 뿐이다.

 

단점

1. 정적 팩터리 메서드만으로는 하위 클래스를 만들 수 없다. 

-> 상속이 불가능하다는 얘기. [불변 타입을 보장해준다는 의미에서 장점이 될 수도 있다]

 

2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

-> 생성자처럼 API 설명에 명확히 드러나지 않으니, 명명방식에 따라 만들고 가져다 쓰는게 지금으로선 최선이다(?)

[12~13p 명명규칙 참조]

 

결론 -> 생성자와 정적 팩터리 메서드는 각자 쓰임새가 있으니 상황에 맞게 쓰되, 정적 팩터리 메서드 장점이 많으므로 되도록 정적 팩터리 메서드를 쓰자.

 

아이템 2 - 생성자에 매개변수가 많다면 빌더를 고려하라

 

Computer pc = new Computer(cpu, power, graphicCard, mainBoard, ssd);

위와 같이 매개변수가 많은 객체가 있다면, 빌더를 사용하는게 낫다. 매개변수의 순서를 기억하기가 어렵다.

Computer pc = Computer.builder()
                      .cpu(cpu)
                      .power(power)
                      .graphicCard(graphicCard)
                      .mainBoard(mainBoard)
                      .ssd(ssd)
                      .bulid();

이처럼 빌더를 사용하면 매개변수의 순서를 기억할 필요 없이 입력받을 매개변수에 맞게 정해진 (일종의setter)메소드로 입력 받으면 된다. (롬복의 builder 패턴을 이용하면 책에 나온 빌더 패턴을 직접 구현하는 것보다 낫다. [소스양도 줄어들고 매개변수 추가 변경시에도 간편])

 

아이템 3 - private 생성자나 열거 타입으로 싱글턴임을 보증하라.

 

싱글턴 패턴을 만드는 방식은 보통 두가지.

1. public static final 필드 방식

public class Fruit {
  public static final Fruit INSTANCE = new Fruit();
  private Fruit() {...}
}

2. 정적 팩터리 방식

public class Fruit {
  private static final Fruit INSTANCE = new Fruit();
  private Fruit() {...}
  public static Fruit getInstance() { return INSTANCE; }
}

 

위 두 방식 다 생성자는 private로 구현하여 실제 객체를 가져오는 방법은 생성자가 아닌 static 필드를 직접 가져오거나,

getInstance와 같은 정적 팩터리 방식으로만 가져올 수 있다. (새로 생성할 수는 없다는 의미이다.)

 

책에서는 이보다 나은 세번째 방식도 소개하고 있는데,

열거 타입으로 싱글턴을 구현하는 방법이다. (가장 바람직한 방법이라고 한다.) [25p참고]

public enum Fruit {
  INSTANCE;
}

 

아이템4 - 인스턴스화를 막으려거든 private 생성자를 사용하라

-> 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만듦. 

사용자는 이렇게 기본으로 만들어진 생성자가 자동 생성된 것인지 의도한 것인지 알 수 없다.

명시적으로 private 생성자를 만들어주어 불필요한 클래스의 인스턴스화를 막을 수 있다.

더불어 private 생성자 내부에 AssertionError를 throw하게 처리해두면, 혹시나 의도적으로 생성을 막아둔 것을

클래스 내부에서라도 생성하는 실수를 막을 수도 있다. (그렇지만 확실한거는 주석해두는게 베스트라고 한다.)