이 블로그는 개인의 공부 목적으로 작성된 블로그입니다. 왜곡된 정보가 포함되어 있을 수 있습니다
1. 생성자 대신 정적 펙터리 메서드를 고려하라
클래스의 인스턴스를 생성할때 생성자를 사용하는 것이 일반적이다. 그러나 책에서는 생성자 대신에 정적 펙터리 메서드(static factory method)를 사용하여 인스턴스를 생성하는 방법을 제시한다. 장점을 떠나 인스턴스를 생성할때의 목적으로 생성자라는 개념이 생겨났는데 왜 정적 메서드를 사용해야 할까? 궁금증은 나중에 생각하고 계속 보자
먼저 정적 메서드를 사용했을경우, 몇가지 장점이 존재한다.
1. 메서드가 이름을 가질 수 있다.
생성자에는 매개변수와 클래스 이름만 알 수 있다. 따라서 반환되는 객체에 대해서 객체를 온전히 이해하기 힘들다는 것이 책의 입장이다. 생성자 오버로딩으로는 매개변수의 종류, 순서만 바꿀 수 있어서 한계가 있다. 이때 정적 메서드의 옳바른 이름을 주어 인스턴스를 생성한다면 생성자의 단점을 보안할 수 있다.
2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
static의 특징과 관련있는 장점이다. 클래스 내부 필드, 메서드를 사용하기 위해서는 인스턴스를 생성해야 하는데 정적 메서드의 경우, 인스턴스를 생성하지 않아도 사용할 수 있기 때문에, 불필요 객체 생성을 예방하여 리소스 측면에서 이득이 있다는 것이다.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
책의 예제 코드 같이 인스턴스를 굳이 생성할 필요없는 경우에 유용하다.
더 나아가서 정적 메서드를 사용할 경우, 인스턴스를 통제할 수 있는데 다음과 같이 싱글톤으로 인스턴스를 관리 할 수 있다.
public class Ticket {
public static Ticket ticket;
String name;
public static Ticket findTicket(){
if(ticket==null){
System.out.println("생성");
return ticket=new Ticket();
}
else{
System.out.println("이미 생성");
return ticket;
}
}
}
Ticket을 인스턴스 생성시 findTicket이라는 정적 메소드를 활용하여 싱글톤을 유지 할 수 있다. 이렇게 인스턴스의 라이프사이클을 통제할 수 있다고 하여 인스턴스 통제 클래스라고 한다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
다형성을 활용할 수 있다는 것인데 구현 클래스를 공개하지 않고도 객체를 반환할 수 있다.
public class Ticket extends Paper{
String name;
public static Paper findPaper(){
return new Ticket();
}
@Override
public void overloadMethod(){
System.out.println("after");
}
}
Paper를 상속하는 Ticket에 대해서 부모 Paper 타입에 자식인 Ticket인스턴스를 넣어 전달하여 활용할 수 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환 할 수 있다.
3번을 활용하여 매개변수에 따라 다른 인스턴스를 반환하도록 할 수 있다.
public class Book extends Paper{
@Override
public void overloadMethod(){
System.out.println("book");
}
}
Paper을 상속하는 새로운 Book 클래스가 존재한다.
public class Config {
public static Paper findPaper(int page){
return new Book();
}
public static Paper findPaper(){
return new Ticket();
}
}
Config에 정의한 정적 메서드에 따라 매개변수에 따른 Book, Ticket 인스턴스를 생성한다.
public class static1 {
public static void main(String[] args) {
Paper paper1=Config.findPaper();
paper1.overloadMethod();
Paper paper2=Config.findPaper(10);
paper2.overloadMethod();
}
}
구현체에서는 각각의 역활이 무엇인지 알 수 없고, 알 필요도 없다!
5. 정적 펙터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
JDBC와 같이 서비스 제공 프레임워크에 이를 따르고 있다고 한다.(이해하지 못했다.)
지금까지는 장점들을 살펴보았다. 5를 제외하고 어느정도 납득이 가는 장점들이다. 그러나 단점도 존재한다.
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를만들 수 없다.
여러 상속관계에서 정적 메서드를 사용하면 하위 클래스의 인스턴스를 생성할 수 없다. (만들려면 일일히 다 넣어야한다.)
책에서는 이러한 제약이 나중에 소개되는 아이템으로 인해 오히려 장점이 된다고 한다.
2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
생성자가 아니기 때문에 정적 팩터리를 사용하는 것 자체가 어려울수 있다.(찾지 못해서) 그래서 책에서는 어느정도 약속된 표현들을 제시하고 있다.
- from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
- of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
- valueOf: from과 of의 자세한 버전
- instance, getInstance: 매개변수로 명시한 인스턴스를 반환, 그러나 같은 타입의 인스턴스가 아닐 수 있음
- create, newInstance: instance, getInstance와 동일하고, 매번 새로운 인스턴스 생성을 보장
- getType: getInstance와 동일하나 다른클래스의 팩터리 메서드를 정의할 때 사용
- newType: newInstanc와 동일하나 생성한 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할때 사용
- type: getType과 newType의 간결한 버전
2. 생성자 대신 정적 펙터리 메서드를 고려하라
1에서의 정적 메서드는 생성자와 동일한 주의사항이 존재하는데 매개변수가 많아 질 수록 적절하게 대용하기 어렵다는 것이다.
기존의 채택되던 방식은 점층적 생성자 패턴으로 재귀적으로 생성자를 호출하도록 구현하는 방식이다.
package item0.item1;
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 NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0, 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;
}
}
위와 같은 점층적 생성자 패턴을 사용할 경우 매개변수가 많아질수록 클라이언트 코드에 부담을 주게 된다. (실수를 유발한다) 또한 위의 경우 모든 매개변수 입력 경우의 수에 대해 대처할 수 없다.
자바빈즈패턴이라는 대체 방법이 있다. 매개변수가 없는 생성자로 객체를 만든 후 setter를 사용해서 초기화하는 방식이다.
public class NutritionFacts {
private int servingSize;//필수
private int servings;//필수
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public NutritionFacts(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}
이 경우, 여러 매개변수 입력 경우에 대해서 대처 할 수 있지만, 가장 치명적인 문제가 발생하는데 구현체에서 일일히 값을 넣어 줘야하기 때문에 객체의 일관성이 유지되지 않는다.(제대로 초기화 되어 있는지 알 수 없다)
그래서 책에서 제시하는 방법은 빌더 패턴이다. 클라이언트는 필요한 객체를 직접만드는대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻고, 빌더 객체가 제공하는 setter로 맴버 변수를 매개변수로 초기화한다. 마지막으로 매개변수가 없는 builder를 호출하고 객체를 리턴하는 형식이다. 빌더하는 정적 클래스로 생성하고자 하는 클래스에 정의한다.
package item0.item1;
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 sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = 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;
}
}
NutritionFacts 내부에 정의되어 있는 정적 클래스 Builder에 맴버 변수를 초기화 하여 생성자에서 Builder 객체의 맴버변수를 NutritionFacts의 맴버변수에 초기화 하는 방식이다.
NutritionFacts cocaCola=new NutritionFacts.Builder(240,8)
.calories(100).sodium(35).carbohydrate(27).build();
'Java' 카테고리의 다른 글
[이펙티브 자바] 아이템 47, 48, 49 (2) | 2024.04.27 |
---|---|
[이펙티브 자바] 아이템 39, 40, 41 (1) | 2024.03.24 |
[이펙티브 자바] 아이템 10, 11 (1) | 2024.01.14 |
[이펙티브 자바] 아이템 7,8,9 (1) | 2024.01.13 |
[이펙티브 자바] 아이템 3,4,5,6 (0) | 2024.01.07 |