이 장에서는 객체를 언제 어떻게 생성하는지, 언제 어떻게 생성을 피해야 하는지, 적합한 방법으로 소멸되는 것을 어떻게 보장하는지, 그리고 객체 소멸에 앞서 선행되어야 하는 클린업 작업을 어떻게 관리할 것인가에 대해 설명한다.
1.생성자 대신 static 팩토리(factory) 메소드 사용을 고려하자
클래스의 인스턴스 생성방법
1. public 생성자(constructor)
2. public static 팩토리 메소드
장점:
– 생성자와 달리 자기 나름의 이름을 가질 수 있다. (알다시피, 생성자는 클래스 이름과 동일해야 한다.) – 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다. – 자신의 클래스 인스턴스만 반환하는 생성자와 달리, static 팩토리 메소드 자신이 반환하는 타입의 어떤 서브타입 객체도 반환할 수 있다. (유연성 제공) – 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.
이런 식으로 중복 지정하면 타입 매개변수가 늘어나는 경우, 타이핑할 분량이 많아지고 복잡해진다.
Map> m = new HashMap>();
아래에 선언한 newInstance 메소드를 다음과 같이 간결한 형태로 사용할 수 있다.
static 팩토리를 사용하면 컴파일러가 타입 매개변수를 해결하도록 할 수 있다. 이것을 타입 추론이라고 한다
Map> m = HashMap.newInstance();
public static HashMap newInstance() {
return new HashMap();
}
1.7버전부터 오른쪽의 제너릭타입은 생략 가능하다.
Map> m = new HashMap<>();
단점:
인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질 수 없다. 하지만 이런 단점이 장점이 될 수도 있다. 프로그래머들이 상속(inheritance) 대신 컴포지션(composition)을 사용하게끔 해주기 때문이다. 다른 static 메소드와 쉽게 구별할 수 없다. 하지만, 공통적인 작명 규칙을 정하여 사용함으로써 단점을 줄일 수 있다.
(예) valueOf, of, getInstance, newInstance, getType, newType
// java.lang.String 일부 발췌
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public static String valueOf(char data[]) {
return new String(data);
}
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
char data[] = {c};
return new String(0, 1, data);
}
public static String valueOf(char c) {
char data[] = {c};
return new String(0, 1, data);
}
public static String valueOf(long l) {
return Long.toString(l, 10);
}
public static String valueOf(float f) {
return Float.toString(f);
}
}
요약 : 인스턴스 생성 방법 2가지 모두 나름의 용도가 있지만, 대개 static 팩토리 메소드가 좋을 때가 많으므로, 팩토리 메소드를 먼저 고려하지 않고 무심코 public 생성자를 사용하지 말자.
2.생성자의 매개변수가 많을 때는 빌더(builder)를 고려하자
static 팩토리 메소드와 생성자는 공통적인 제약이 있다. 선택 가능한 매개변수가 많아질 경우, 신축성 있게 처리하지 못한다. 그 문제를 해결할 3가지 방법에 대해 설명한다.
1. 텔리스코핑 생성자(telescoping constructor) 패턴
필수 매개변수들만 갖는 생성자, 필수 매개변수들과 선택 매개변수 하나를 갖는 생성자, 필수 매개변수들과 선택 매개변수 두 개를 갖는 생성자 등의 형태로
모든 선택 매개변수를 생성자가 가질 수 있도록 여러 개의 생성자를 만드는 것이다.
[단점] 그런대로 쓸만하지만, 매개변수가 많아지면 코드 작성이 힘들고, 가독성이 떨어진다.
// 텔리스코핑 생성자 패턴 - 신축성이 좋지 않다!
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
2. 자바빈즈(JavaBeans) 패턴 – 텔리스코핑 생성자 패턴 단점 해결
매개변수가 없는 생성자를 호출해서 객체를 생성한 후 세터(setter) 메소드를 호출하여 각각의 필수 필드와 선택 필드 값을 지정한다.
[단점] 여러 번의 메소드 호출로 나누어져 인스턴스가 생성되므로, 생성과정을 거치는 동안 자바빈 객체가 일관된 상태를 유지하지 못할 수 있다.
// 자바빈즈 패턴 - allows inconsistency, mandates mutability
public class NutritionFacts {
// 디폴트 값으로 필드 값을 초기화한다
private int servingSize = -1; // Required; no default value
private int servings = -1; // " " " "
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
3. 빌더(Builder) 패턴 – 텔리스코핑 생성자, 자바빈즈 패턴 단점 해결
장점 – 코드 작성이 쉽고 가독성이 좋다. – 또 여러 개의 가변인자 매개변수를 가질 수 있다. (세터 당 하나씩)
단점 – 어떤 객체를 사용하려면 우선 그것의 빌드를 생성해야 한다. – 성능이 매우 중요한 순간에 빌더 객체 생성 비용이 문제가 될 수 있다.
// 빌더 패턴
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// 필수 매개변수들
private final int servingSize;
private final int servings;
// 선택 매개변수들 - 디폴트 값으로 초기화한다
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val)
{ calories = val; return this; }
public Builder fat(int val)
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
}
}
요약 : static 팩토리 메소드와 생성자에서 많은 매개 변수를 갖게 될 경우(4개 이상)에는 빌더 패턴이 좋은 선택이다. 특히 선택 매개변수가 대부분인 경우에 사용하면 좋다.
번외 JavaBeans, VO, DTO 에 대해…
"전달 혹은 조작이 가능하도록 속성에 대한 접근자를 제공하며 기본 생성자를 반드시 갖는 객체"
VO (Value Object)
값 개체 그 자체를 말한다고 합니다.
전 일반적으로 JavaBeans 규약을 따르는(그냥 기본 생성자에 get, set 로 필드에 접근할 수 있다면) 그것을 VO라 불렀는데, 좀더 정확하게는 그런 것이 아니더군요.
값 자체를 나타내며 일반적으로 자바에서는 다른 값들의 집합 객체으로 표현됩니다.
그리고 불변(immutable) 입니다!!
이펙티브 자바 책에서도 특별한 이유가 없다면 모든 값 객체는 불변으로 디자인 하는걸 추천했던 것 같습니다.
만일 새로운 값이 필요하다면 새로 new 연산자를 쓰는 것이 추천된다고 합니다
흥미로운 포스트 하나 링크합니다
object generator library는 immutable class를 만들수없다
DTO(Data Transfer Object)
예전 EJB 에서 사용되던 패턴이라고 합니다. 각 계층간 데이터 교환 시에 데이터를 직접 하나하나 전달하거나 가져가지 않고 객체를 하나 만들어서 그것을 전달하는 패턴이죠.
EJB에서는 각 계층이 네트워크로 묶일 때가 많아서 계층간 작은 데이터의 작은 교환이 성능 저하로 이어지기에 DTO가 필수였다고 하는군요.
DTO에는 절대 비즈니스 로직같은 특정 액션이 들어가서는 안되고 정말 단순한 값의 전달만을 위한 메서드만을 허용한다고 합니다.
대부분 위에 이야기한 JavaBeans 규약을 따라 만들어지고 사용됩니다. 개념이나 목적의 차이가 있을 뿐 내부 구현은 큰 차이가 없기에 보통 VO라고 부르는 등 많이 섞어 부릅니다.
요약하면
"각 계층간 데이터 교환을 위한 자바빈즈" 최근 풍성한 도메인 방식이 유행하고 있는데 그런 방식에서는 거의 사용될일이 없다고도 볼 수 있겠네요.
creative by javacafe