
@Builder 패턴의 이해
점층적 생성자 패턴의 안전성과 빈즈 패턴의 가독성을 더해 유동적으로 객체를 생성할 수 있는 패턴입니다.
기존 생성자가 가진 단점
public class Article { private long id; private String title; private String content; }
1. 생성자를 일일이 만들기 귀찮다
위와 같은 클래스 Article이 있다고 가정합시다. Article은 id와 title, content를 필드로 가지고 있습니다.
이때 생성자를 이용한 생성 방법은 아래와 같습니다.
public class Article { private long id; private String title; private String content; // 아래부터 생성자 Article(String title, String content){ this.title = title; this.content = content; } Article(String title){ this.title = title; // content = null 로 생성 } Article(String content){ this.content = content; // title = null로 생성 } Article(){ // content, title, null 로 생성 } }
@NoArgsConstructor, @AllArgsConstructor을 제외한다면,
특정 필드만 생성인자로 가지고 있는 생성자는 이와 같이 일일이 작성해야 하는 단점이 있습니다.
2. 클래스 내부의 필드가 많아지면 가독성이 떨어진다.
Person person = new Person(red, 2, 180, 70, korean, korea, male, bald, biba, Lee); // 각 인자가 뭐에 대한 필드인지는 몰?루 ㅋㅋ
위 코드를 통해 클래스 내부의 필드가 많아지면 아래와 같은 생성자의 단점이 있음을 알 수 있습니다.
1. 가독성 문제 : 생성자만으로는 각각의 인자가 무엇을 나타내는지 모른다.
2. 순서의 강제성 : 필드가 많은 것도 헷갈리는데 각 인자의 순서도 강제되어 있다.
위와 같은 생성자의 단점을 보완할 수 있는 패턴이 바로 @Builder입니다.
사용 방법
클래스 위에 @Builder 를 선언합니다.
이후 Builder 클래스를 불러와 새로운 인스턴스를 생성합니다.
[@Builder 선언 방법] : 클래스 위에 @Builder를 선언합니다.
@Builder public class Article { private long id; private String title; private String content; }
[@Builder를 활용한 객체 생성 방법] : 함수형으로 각 필드별 값을 집어넣습니다.
// #1. All Args Article article1 = article.Builder() .id(1L) .title("hello") .content("world") .build(); // #2. 순서가 바뀌어도 상관없다. Article article2 = article.Builder() .id(1L) .content("world") .title("hello") .build(); // #3. 특정 필드값을 명시하지 않아도 객체는 생성된다. (별도 초기화값을 지정할 수도 있다) Article article3 = article.Builder() .id(1L) // .title("hello") : 이 경우 title이 생략되면 title 값은 null로 기본 설정 .content("world") .build();
[@Builder.Default 활용 초기화 필드 값을 설정하는 방법] : @Builder. Default를 명시하고 초기화 값을 선언합니다.
@Builder public class Article { private long id; @Builder.Default private String title = "empty title"; @Builder.Default private String content = "empty content"; }
Article article1 = article.Builder() .id(1L) .build(); // title : test title, content : test content
예시는 빌더를 사용해 객체를 생성하고 있으며, title과 content 필드 값을 집어넣지 않았습니다.
이 경우 title, content 필드는 각각 @Builder. Default로 명시한 초기화 값인 test title, test content로 생성됩니다.
@Builder의 패턴의 장점
- 가독성 있는 객체 생성 가능 : 각 필드값에 무슨 값이 들어가는지 명료합니다.
- 객체 불변성 유지 : 생성자로 인한 개념과 동일하며, 최초 빌더로 인한 생성 시에 값이 정해집니다.
- 가변 인자 지원 : 다양한 필드 값을 동적으로 생성합니다.
@ToBuilder
@Builder의 파생 어노테이션입니다.
빌더 패턴을 통해 생성된 기존 객체 불변성을 유지하며 일부 속성을 변경하고자 할 때 사용합니다.
사용 방법
클래스 위에 @Builder (toBuilder = true) 로 투빌더를 사용함을 선언합니다.
이후 Builder 클래스로 생성된 객체를 통해 toBuilder를 불러와 사용합니다.
Article article = article.Builder() .id(1L) .title("hello") .content("world") .build(); // update article article = article.toBuilder() .title("goodbye") // 수정할 필드 값을 명시 후 변경 .build();
위의 코드는 article을 toBuilder 클래스를 통해 title 필드의 값을 업데이트하는 예시입니다.
Q. toBuilder를 사용하면 Setter와 같이 객체에 대한 필드 값을 갱신하는데 불변성이 보장되는 이유?
객체. toBuilder()는 해당 객체의 필드 속성을 가지고 있는 빌더 클래스 객체, 즉 객체의 복제본을 생성합니다.
해당 복제본은 기존 객체의 모든 필드값을 가지고 있으며, toBuilder는 이 중 변경되는 필드값에 따라 복제본의 값을 변경해 리턴하기 때문에, 기존 객체의 불변성은 유지되는 동시에 객체의 필드 값을 업데이트할 수 있습니다.
Setter는 객체를 생성한 이유 그 객체의 필드값을 직접적으로 변경을 가한다는 점에서 불변성이 보장되지 않습니다.
Article article = article.Builder() .id(1L) .title("hello") .content("world") .build(); // toBuilder를 사용하여 article을 복제한 다른 객체를 생성하는 방법 Article article2 = article.toBuilder() .title("goodbye") .build(); // article의 복제본을 생성, 특정 필드값을 변경 후 덮어씌우는 방법 // 이 경우 article 변수가 기존에 참조하던 article 객체는 가비지 콜렉션에 의해 덤프됨 article = article.toBuilder() .title("haha") .build();
즉, toBuilder 클래스를 통해 기존의 객체 복제본을 생성해
1. 기존 객체의 특정 필드값을 변경해 기존 객체에 덮어 씌우는 기능도 가능하며,
2. 기존 객체의 불변성을 유지한 채로 다른 객체로 복제할 수도 있습니다.
+ 객체의 불변성을 유지해야 하는 이유
객체의 불변성 : 객체가 최초 생성된 시점 이후 상태 값이 변하지 않는 성질을 의미합니다.
보통 final로 선언된 객체와 필드가 이에 해당합니다.
[객체의 불변성이 가져오는 장점]
- 쓰레드 간 간섭에도 객체의 불변성이 보장되면 데이터 무결성도 확립됩니다.
- 생성 시점 이후에도 해당 객체 상태를 변경할 수 없으므로 동기화 문제를 걱정하지 않아도 됩니다.
- 즉, 동시성이 중요한 프로그램에서 객체의 상태 변화로 인한 여파를 걱정하지 않아도 됩니다.
[객체의 불변성을 유지하는 방법]
- final 클래스와 모든 필드를 final로 선언
- 모든 필드의 접근 제어자를 private로 선언
- 생성자를 이용하되 Setter 메서드를 사용하지 않기 (최초 생성 시점 이후로 불변)
- Builder 패턴도 엄연히 3번의 패턴을 준용하기에 객체의 불변성이 유지됨
'DEV > Java' 카테고리의 다른 글
[Java][lombok] @ToString에 대한 이해와 적용 (0) | 2023.11.14 |
---|---|
[java] 가변 인자(variable args) 설명 및 예시 (1) | 2023.11.10 |
[java] 스트림 도전기 (3 / 3) - 스트림 생성 ~ 연산까지 (0) | 2023.10.26 |
[java] json.simple 활용 JSON 데이터 저장, 불러오기 (0) | 2023.10.25 |
[java] NoSuchElementException 예외의 이해와 발생사례 (1) | 2023.10.25 |