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

gongmeda·2022년 1월 15일
0

Effective Java

목록 보기
4/4
post-thumbnail

점층적 생성자 패턴과 자바빈즈 패턴의 제약

점층적 생성자 패턴

public class Cat {
    private final int age;
    private final int weight;
    private final int intelligence;
    private final int friendliness;
    private final int escapeCount;

    public Cat(int age, int weight) {
        this(age, weight, 0);
    }

    public Cat(int age, int weight, int intelligence) {
        this(age, weight, intelligence, 0);
    }

    public Cat(int age, int weight, int intelligence, int friendliness) {
        this(age, weight, intelligence,  friendliness, 0);
    }

    public Cat(int age, int weight, int intelligence, int friendliness, int escapeCount) {
        this.age = age;
        this.weight = weight;
        this.intelligence = intelligence;
        this.friendliness = friendliness;
        this.escapeCount = escapeCount;
    }
}
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat(5, 4, 100, 1, 55);
    }
}
  • 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려움
  • 실수로 매개변수의 순서를 바꿔 건네줘도 컴파일러는 알아채지 못하고 엉뚱한 동작을 함

자바빈즈 패턴

public class Cat {
    private int age = -1;
    private int weight = -1;
    private int intelligence = 0;
    private int friendliness = 0;
    private int escapeCount = 0;

    public Cat() {}

    public void setAge(int val) {
        this.age = val;
    }

    public void setWeight(int val) {
        this.weight = val;
    }

    public void setIntelligence(int val) {
        this.intelligence = val;
    }

    public void setFriendliness(int val) {
        this.friendliness = val;
    }

    public void setEscapeCount(int val) {
        this.escapeCount = val;
    }
}
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.setAge(5);
        cat.setWeight(4);
        cat.setIntelligence(100);
        cat.setFriendliness(1);
        cat.setEscapeCount(55);
    }
}
  • 객체 하나를 만들려면 메서드를 여러개 호출해야 함
  • 객체가 완성되기 전까지는 일관성(consistency)이 무너진 상태에 놓임
  • 따라서, 클래스를 불변으로 만들 수 없음 -> 스레드 안전성X (freeze 메서드를 구현하여 스레드 안전성을 부여할 수는 있지만 실전에서 잘 사용하지 않음)

빌더 패턴

public class Cat {
    private final int age;
    private final int weight;
    private final int intelligence;
    private final int friendliness;
    private final int escapeCount;

    public static class Builder {
        private final int age;
        private final int weight;

        private int intelligence = 0;
        private int friendliness = 0;
        private int escapeCount = 0;

        public Builder(int age, int weight) {
            this.age = age;
            this.weight = weight;
        }

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

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

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

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

    private Cat(Builder builder) {
        age = builder.age;
        weight = builder.weight;
        intelligence = builder.intelligence;
        friendliness = builder.friendliness;
        escapeCount = builder.escapeCount;
    }
}
Cat cat = new Cat.Builder(5, 4)
                .intelligence(100)
                .friendliness(1)
                .escapeCount(55)
                .build();

장점

  • 빌더 패턴을 사용하는 클래스는 불변임
  • 빌더의 세터 메서드는 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다 = 메서드 연쇄(method chaining)
  • 클라이언트 코드가 쓰기 쉽고, 읽기도 쉽다
  • 계층적으로 설계된 클래스와 함께 쓰기에 좋다
public class Cat {
    protected final int age;
    protected final int weight;

    abstract static class Builder<T extends Builder<T>> {
        private final int age;
        private final int weight;

        protected Builder(int age, int weight) {
            this.age = age;
            this.weight = weight;
        }

        abstract Cat build();

        protected abstract T self();
    }

    Cat(Builder<?> builder) {
        age = builder.age;
        weight = builder.weight;
    }
}
public class MagicianCat extends Cat {
    private int mana;

    public static class Builder extends Cat.Builder<Builder> {
        private int mana;

        public Builder(int age, int weight) {
            super(age, weight);
        }

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

        @Override
        public MagicianCat build() {
            return new MagicianCat(this);
        }

        @Override
        protected Builder self() {
            return null;
        }
    }

    private MagicianCat(Builder builder) {
        super(builder);
        mana = builder.mana;
    }
}
public class WarriorCat extends Cat {
    private int strength;

    public static class Builder extends Cat.Builder<Builder> {
        private int strength = 0;

        public Builder(int age, int weight) {
            super(age, weight);
        }

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

        @Override
        public WarriorCat build() {
            return new WarriorCat(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private WarriorCat(Builder builder) {
        super(builder);
        strength = builder.strength;
    }
}
public class Main {
    public static void main(String[] args) {
        WarriorCat cat1 = new WarriorCat.Builder(5, 8)
                .strength(100)
                .build();

        MagicianCat cat2 = new MagicianCat.Builder(5, 8)
                .mana(55)
                .build();
    }
}

단점

  • 객체를 만들기 위해 빌더부터 만들어야 함 (빌더 생성 비용이 크지 않지만, 성능에 민감한 상황에서는 문제가 될 수 있음)
  • 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다
profile
풀스택의 꿈

0개의 댓글