Home 빌더 패턴의 불완전성 없애기
Post
Cancel

빌더 패턴의 불완전성 없애기

1. 글을 작성하게 된 계기


빌더 패턴(Builder Pattern)은 불완전해서 사용할 때 주의해야 한다는 이야기를 많이 합니다. 하지만 이를 보완하는 방법도 존재하는데요, 이를 소개하기 위해 글을 작성하게 되었습니다.





2. 빌더 패턴이 불완전한 이유


빌더 패턴은 객체가 생성되기 전, 필드를 자유롭게 추가 할 수 있으며, 사이드 이펙트 를 예측하기 힘들기 때문에 주의해야 합니다. 예를 들어, 다음과 같이 Shop 클래스의 인스턴스를 빌더 패턴으로 생성해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Getter
public class Shop extends BaseEntity {
    private final Long id;
    private Long userId;
    private final String uniqueNumber;
    private final String name;

    private Shop(
        String uniqueNumber,
        String name
    ) {
        this(null, uniqueNumber, name);
    }

    @Builder
    private Shop(
        Long id,
        String uniqueNumber,
        String name
    ) {
        this.id = id;
        this.uniqueNumber = uniqueNumber;
        this.name = name;
    }

    ......

}







Shop 인스턴스가 생성되기 전, 필드를 자유롭게 추가할 수 있습니다. 마치 Setter가 있는 것처럼요. 생성자가 private이어도 @Builder를 사용하면 필드를 추가할 수 있습니다.

1
2
3
4
5
6
7
8
9
public class Main {
    public static void main() {
        Shop shop = Shop.builder()
            .id(1L)
            .name("Vips")
            .uniqueNumber(UUID.randomUUID().toString())
            .build();
    }
}







아래와 같이 사용하지 않지만, 이렇게 잘못 사용할 수도 있죠. 즉, 빌더 패턴은 객체가 생성되기 전, 필드를 자유롭게 추가할 수 있으며, 또 언제 어디서 부작용이 발생할지 예측할 수 없기 때문에 사이드 이펙트를 예측하기 어려워집니다. 잘못 사용할 수도 있고요.

1
2
3
4
5
6
7
8
9
10
public class Main {
    public static void main(){
        ShopBuilder shopBuilder = Shop.builder()
            .id(1L)
            .name("Vips");

        Shop newShop = shopBuilder.uniqueNumber(UUID.randomUUID().toString())
            .build();
    }
}







3. 해결 방법


이는 정적 메서드(Static Method)@Builder 를 사용해 문제를 해결할 수 있습니다. 생성자는 private 으로 두고요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Getter
public class Shop extends BaseEntity {
    private final Long id;
    private Long userId;
    private final String uniqueNumber;
    private final String name;

    // private 생성자
    private Shop(
        String uniqueNumber,
        String name
    ) {
        this(null, uniqueNumber, name);
    }

    ......

    /**
     * static 메서드에 @Builder 사용
     * */
    @Builder
    public static Shop createShop(
        String uniqueNumber,
        String name
    ) {
        validate(uniqueNumber, name);
        return new Shop(uniqueNumber, name);
    }

    private static void validate(
        String uniqueNumber,
        String name
    ) {
        if (uniqueNumber == null || uniqueNumber.isBlank()) {
            throw new IllegalArgumentException("Unique number cannot be empty.");
        }
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be empty.");
        }
    }
    
    ......

}







필수 인자 가 누락되면 안되기 때문에 validate 메서드를 사용해 이를 체크해야 합니다. 이때 도메인 규칙이 있다면 이도 함께 검증할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Getter
public class Shop extends BaseEntity {
    
    ......
    
    @Builder
    public static Shop createShop(
        String uniqueNumber,
        String name
    ) {
        validate(uniqueNumber, name);
        return new Shop(uniqueNumber, name);
    }

    ......

}







다만 정적 메서드가 사용되기 때문에 생성자 내부에 들어가는 메서드는 반드시 static으로 사용해야 한다는 단점이 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Getter
public class Shop extends BaseEntity {

    ......

    @Builder
    public static Shop createShop(
        String uniqueNumber,
        String name
    ) {
        validate(uniqueNumber, name);
        return new Shop(uniqueNumber, name);
    }

    private static void validate(
        String uniqueNumber,
        String name
    ) {
        if (uniqueNumber == null || uniqueNumber.isBlank()) {
            throw new IllegalArgumentException("Unique number cannot be empty.");
        }
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be empty.");
        }
    }
    
    ......







3. 정리


빌더 패턴은 불완전합니다. 극단적으로 빌더 패턴을 사용하지 말라는 분도 봤는데요, 여기에는 동의하지 못하겠습니다. 정적 메서드를 통해 이를 보완할 수 있으며, 상황에 맞게 사용하면 되니까요. 여튼 이런 방법이 있다는 것도 알고 빌더 패턴을 잘 사용할 수 있으면 합니다.


This post is licensed under CC BY 4.0 by the author.

서킷브레이커, 재시도 동작순서 삽질기

IAC 도입과 사용하며 느낀점