본문 바로가기
개인 공부/Java (이펙티브 자바)

[이펙티브 자바] 아이템 2 : 생성자에 매개변수가 많다면 빌더를 고려하라

by 희조당 2022. 12. 21.
728x90

✍️ 학습 목표

  • 객체 생성하기 (feat. 생성자)
  • 각각의 장단점 이해하기

📌 객체 생성하기 (feat. 생성자)

생성자를 사용해서 객체를 사용하는 방법은 3가지가 존재한다.

각 방식의 사용법들은 다음과 같다.

1️⃣ 점층적 생성자

매개변수가 다른 생성자를 여러 개 구현하는 방식이다.

필수 매개변수가 들어가는 생성자부터 선택 매개변수를 하나하나 추가되는 생성자를 점층적으로 구현하는 방식이다.

class Pokemon {
    private final int attack, defense, hp;
    
    public Pokemon(int attack) {
        this(attack, 0, 0);
    }

    public Pokemon(int attack, int defense) {
        this(attack, defense, 0);
    }

    public Pokemon(int attack, int defense, int hp) {
        this.attack = attack;
        this.defense = defense;
        this.hp = hp;
    }
}
  • 매개 변수가 많아지면 구현해야할 생성자의 수가 늘어난다.
  • 매개 변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵고, 실수에 취약하다.

2️⃣ 자바빈스 패턴

매개변수가 없는 생성자로 객체를 만들고 세터를 사용하는 방식이다.

class Pokemon {
  private int attack = 0;
  private int defense = 0;
  private int hp = 0;

  public Pokemon() { };
        
  public void setAttack(int attack) { this.attack = attack; }
  public void setDefense(int defense) { this.defense = defense; }
  public void setHp(int hp) { this.hp = hp; }
}
  • 객체 하나를 위해서 많은 메소드들을 호출해야 한다.
  • 객체가 완성되기 전에는 일관성이 무너진 상태이다. (setter로 속성이 계속 변할 수 있는 상태)
  • 따라서 클래스를 불변의 상태로 만들 수 없고 스레드 안정성 또한 보장되지 않는다.
  • 이런 단점을 보완하기 위해서 객체를 얼리는 방법도 있지만 추천되는 방법이 아니다.

3️⃣ Builder 패턴

클라이언트가 직접 필요한 객체를 구성하는 대신 정말 필요한 매개변수로만 생성자를 호출하는 방식이다.

class Pokemon {
    private final int attack, defense, hp, speed;
    
    static class Builder {
        private final int attack, defense, hp; // 필수 매개변수
        private int speed = 5; // 선택 매개변수 - 기본값으로 초기화

        public Builder(int attack, int defense, int hp) {
            this.attack = attack;
            this.defense = defense;
            this.hp = hp;
        }

        public Builder speed(int val) {
            speed = val;
            return this;
        }

        public Pokemon build() {
            return new Pokemon(this);
        }
    }

    private Pokemon(Builder builder) {
        attack = builder.attack;
        defense = builder.defense;
        hp = builder.hp;
        speed = builder.speed;
    }
}

public class BuilderPattern {
    public static void main(String[] ars) {
        Pokemon 피카츄 = new Pokemon.Builder(5, 5, 5).build();
        // 꼬부기는 속도가 느리다..!
        Pokemon 꼬부기 = new Pokemon.Builder(5, 5, 5).speed(0).build();
    }
}
  • 세터 메서드들을 연쇄적으로 호출해서 직관적으로 객체를 생성할 수 있다. 
  • 외부에서 생성된 객체를 수정할 수 없고, build() 로 객체의 생성을 명시하기 때문에 불변성을 보장할 수 있다.

빌더 패턴은 계층적으로 설계된 클래스에서 사용하기 좋은데, 하위 클래스의 조건에 맞게 변경할 수 있기 때문이다.

😋 정리

Builder 패턴은 생성하는 객체의 불변성을 보장할 수 있고, 매우 유연한 방식이다.

하지만 무조건적으로 좋은 것은 아니므로 생성자의 매개변수가 많다면 사용을 고려해 보자!

 

댓글