본문 바로가기

Language/Java Plus

Effective JAVA - Item1 : 생성자 대신 정적 팩토리 메서드를 고려하라.

 

 

이펙티브 자바 2장. 객체 생성과 파괴 아이템 1 : 생성자 대신 정적 팩토리 메서드를 고려하라.

 

결론 : "아래의 장단점을 고려해 생성자 대신 static 키워드를 사용해 메서드를 만들고 이를 활용해 인스턴스를 생성하여 사용하자. " 

 

 

 

 

클라이언트가 클래스의 인스턴스를 얻는 수단

- public 생성자 -> static factory method

 

static factory method 예시

boolean 기본 타입의 boxed class -> Boolean

Boolean의 메서드 valueOf()는 static을 사용해 생성자를 통한 인스턴스 생성없이 Boolean클래스를 참조할 수 있게 한다.

 

기본적인 예를 들어 Boolean 클래스의 메서드를 사용하고 싶은데

Boolean a = new Boolean();

a.method(); 가 아닌

Boolean.method() 로 사용 가능하다.

 

 

 

 

 

Static(정적 메서드)의 장점

 

 

1. 이름을 가질 수 있어 시그니처의 종류가 많은 상황에 용이하며 용도에 대해 이해도를 높일 수 있다.

 

- 생성자 사용시 생성자에 넘기는 매개변수의 이름, 생성자 자체만으로는 반환될 객체의 특성을 설명하지 못한다.

- 정적 메서드의 이름을 잘지으면 해당 객체의 특성을 설명하기 쉽다.

 

예를 들어, Member라는 클래스가 있고 필드로 이름, 나이, 성별, 이메일 정도를 갖고 있다고 가정하자.

이 때, 개발자는 (이름, 나이)를 입력 받아 객체를 생성하거나 (이름, 이메일)을 입력받아서 객체를 생성하고 싶다.

하지만 모든 칼럼은 String으로 생성되어 있다고 가정하자

 

생성자 사용

public Member(String name, String age){};

public Member(String name, String email){};

-> 이 둘은 같은 타입의 매개 변수 같은 메서드 이름(시그니처)를 사용하기에 구별이 되지 않는다.

 

이러한 경우

public static Member makeWithNameAge(String name,  String age){}

public static Member makeWithNameEmail(String name,  String email){}

-> 위와 같이 사용시 용이하다.

 

 

만약, 한 클래스 내의 시그니처(메서드 이름과 입력 매개변수의 타입)가 여러개 필요한 상황이라면 생성자를 정적 메서드로 바꾸고 각각의 특징을 표현하는 이름으로 변경을 하자.

 

 

 

2. 호출될 때마다 인스턴스가 매번 생성되지 않는다.

 

- 인스턴스 생성 없이 해당 클래스의 메서드들을 기본적으로 사용 가능하다.

- 불변 클래스(아이템 17)는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 플라이 웨이트 패턴도 이와 같은 방식을 기초로 한다.

 

예를 들어, Boolean.valueOf(true)는 객체를 아예 생성하지도 않는다.

Boolean 클래스를 가보면 상단에 이미 public static final Boolean True = new Boolean(true);로 이미 객체 생성을 해두었다.

따라서, 생성하지 않고 이미 프로그램 시작시 생성한 인스턴스를 반환해준다.

 

 

- 반복되는 요청에 같은 객체를 반환하는 방식을 사용하여 언제 어느 인스턴스가 살아있게 할지 인스턴스 통제가 가능하다. 이를, 인스턴스 통제 클래스라고 한다.

 

인스턴스를 통제하는 이유 : 결과적으로는 인스턴스의 낭비를 막는다.

- 싱글톤(객체 하나를 만들어 계속 사용) 또는 인스턴스화 불가로 만들 수 있다

- 불변 값 클래스에서 동치인 인스턴스(매개변수가 같은)가 하나임을 보장할 수 있다.

(a==b, a.equals(b)가 성립, 같은 값을 갖는 인스턴스는 하나임을 보장하기 위해 주소도 동일해야 함을 의미.)

- 플라이 웨이트 패턴의 근간이 된다.

- 열거타입은 인스턴스가 하나만 만들어짐을 보장한다.

 

 

 

 

자주 사용되는 인스턴스 같은 경우 클래스 내에 static 인스턴스를 생성해두고 static 메서드를 통해 이를 반환해주는 식으로 다른 클래스에서 해당 인스턴스를 사용하여 인스턴스의 낭비를 줄이자.

 

 

 

 

 

 

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

 

- 반환하려는 클래스를 공개하지 않고 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다.

 

예를 들어, Member라는 인터페이스가 있고 이를 구현한(implements) Owner와 User 클래스가 있다고 가정하자. 이 때, 특정 클래스에서 (예를 들어 팩토리클래스) static 메서드의 반환타입이 Member라고 가정하자. 이 때, 반환을 Owner, User로 반환해도 무방하다.

 

public static Member 메서드(){
	return Owner;
}

 

 

 

 

 

 

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

 

만약 밑의 예시를 통해, Memer member = Member.createMember("Owner", "이름1")라고 했다고 가정했을 때, 

이름1이라는 Owner객체가 반환되는 것이다. 또, Member.createMember("User", "이름2")라고 했을 때, 이름1을 가진 User객체가 반환된다. 이처럼 매개변수를 통해 매번 다른 객체를 반환할 수 있다.

 

public static Member getMember(String type, String name){
    if(type.equals("Owner")){
    	return new Owner(name);
    }
    return new Customer(name);
}

 

 

 

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

 

static으로 객체 생성하는 시점에 클래스의 이름을 보내고 해당 메서드 내에서는 Class<?> 로 선언한 객체를 .forName()으로 클래스명을 받아와 생성하고 반환하면 된다.

 

 

 

 

 

 

 

Static(정적 메서드)의 단점

 

1.  상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 해당 클래스의 하위 클래스를 만들 수 없다.

예를 들어보면, 정적 팩토리만을 제공하기 위한 클래스를 만들었다고 가정하자. 해당 클래스의 생성자를 하나 만들고 private로 설정하여 이 클래스는 정적 팩토리 메서드만을 위한 클래스임을 명시하는 경우가 대부분이다. 그리고 정적메서드를 이용해서만 인스턴스를 생성할 수 있는 생성자 없는 클래스가 있다고 가정하자. 이러한 경우, 현재 해당 클래스의 하위 클래스를 생성하는 것은 불가능하게 된다. 

 

하지만 상속보다는 컴포지션(객체 안에 이미 필요한 다른 객체가 들어가 있는 형태)을 사용하도록 유도하고 불변타입을 만들기 위해서는 이 제약을 지켜야한다는 점에서 장점으로 보는 시각 또한 존재한다.

 

컴포지션

- DAO에 Repository 넣는것과 같은 구조로 생각하면 간편하다.

- 위의 예시에서는 static 메서드들을 위한 클래스를 만들었다고 가정했을 때, 해당 클래스를 사용하기 위해서 다른 클래스에서 이를 상속을 받아서 사용할 수 없기에 선언 후 주입하는 형태로 사용을 해야한다. 이런 방식으로 오히려 컴포지션을 유도해 객체 지향의 원칙에 좀 더 부합하다는 시각이 있음을 의미한다. 부가적으로는 예를 들어, Collections를 사용하기 위해서 import해서 사용하는 것과 같은 원리이다.

 

 

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

하나의 클래스 내에는 많은 정적 팩터리 메서드가 있을 수 있고 이를 모두 이해하며 사용하기란 어렵다는 의미이다.

따라서, 일정한 규칙을 통해 조금은 이해하기 쉽게 유도해서 메서드 명을 작성 가능하다.

 

from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드

of : 매개변수를 여러개 받아서 적절한 타입의 인스턴스를 반환하는 형변환 메서드

valueOf : from, of 의 좀 더 자세한 버전

instance/getInstance : 매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하지 않는다.

create/newInstance : 위와 동일하지만 매번 새로운 인스턴스임을 보장한다.

get타입명 : 팩토리 메서드가 반환할 객체의 타입을 메서드 명에 지정해놓고 반환한다.

new타입명 : 팩토리 메서드가 반환할 객체의 타입이 자신이 아닐 때, 즉 다른 클래스일 때 지정해서 반환한다.

타입명 : 위 두개의 간단한 버전

ex) Collections.list();

 

 

 

 

 

적용

- 아래는 회원이 비밀번호 수정을 위해 사용되는 내용을 담는 클래스이다.

- 기본적으로 스프링에서 직렬화 진행시 기본생성자를 통해 객체를 생성하고 getter/setter를 통해 내용을 담아서 서버에 전송하기 때문에 생성자 아예 없이 static 메서드로만 인스턴스를 생성하는 것은 불가능하다.

- 하지만 테스팅 용도로 sample을 만들어 사용하는데 아래와 같이 작성시 프로그램 실행시 sample 인스턴스를 생성하고 static 메서드를 통해 전송해주면 이름을 통해 용도를 쉽게 알 수 있고 인스턴스도 미리 생성한 인스터스를 반환하기에 인스턴스 비용이 줄게 된다.

 

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class ModifyMemberDTO {
  
  private static final ModifyMemberDTO sample = 
      ModifyMemberDTO.builder().userPwd("testPwd").userName("testName")
      .userAddress("testAddress").userEmail("testEmail").build();

  @NotNull
  private String userPwd;

  @NotNull
  private String userName;

  @NotNull
  private String userNumber;

  @NotNull
  private String userAddress;

  @NotNull
  private String userEmail;


  public static ModifyMemberDTO sample() {
    return sample;
  }
}

 

 

 

코드 정리

https://github.com/mokjaemin/EffectiveJAVA

 

GitHub - mokjaemin/EffectiveJAVA: Study Files Of Effective JAVA

Study Files Of Effective JAVA. Contribute to mokjaemin/EffectiveJAVA development by creating an account on GitHub.

github.com