💡 개요
자바에서 많은 멤버변수를 가진 클래스 객체를 생성할 땐 가독성과 매개변수 파악을 위해 빌더 패턴을 자주 사용한다.
오늘은 이 빌더 패턴을 사용하는데, 생성자가 private 이라도 클래스 외부에서 접근 가능한 이유에 대해 정리해 보자.
📕 new() vs Builder
빌더 패턴이란 객체 생성과정에서 많은 매개변수를 입력해야 할 땐, 잘못된 매개변수를 입력할 가능성이 있다.
이런 상황을 대비하고, 객체 생성을 더 안전하게 하기 위해 만들어진 디자인 패턴이다.
🚀 new 메서드
다음은 많은 멤버 변수를 사용하는 Member 클래스와 생성자이다.
class Member {
private Long id;
private String name;
private String nickname;
private int age;
private String phone;
private String address;
Member(Long id, String name, String nickname, int age, String phone, String address) {
...
}
}
Member member = new Member(1L, "홍길동", "길동닉네임", 25, "010-1234-5678", "경상북도 구미시 ...");
Member 객체를 생성하는 코드를 보면 많은 매개변수가 들어가 있다.
만약 Member 객체를 생성하는 코드가 다음과 같이 변하면 어떻게 될까?
Member member = new Member(1L, "길동닉네임", "홍길동", 25, "010-1234-5678", "경상북도 구미시 ...");
코드를 보면 String name 과 String nickname 의 입력 매개변수 순서가 뒤바꿔져 있다.
비즈니스 적으로 잘못된 코드이다. 하지만 위의 문제는 컴파일러는 알아챌 수 없고, 개발자 입장에서도 눈치채기 힘들다.
이러한 문제점을 극복하기 위해 빌더 패턴을 사용한다.
🚀 Builder 패턴
다음은 Member 에 빌더 패턴을 직접 적용한 코드이다.
public class Member {
private Long id;
private String name;
private String nickname;
private int age;
private String phone;
private String address;
private Member(MemberBuilder builder) {
this.id = builder.id;
this.name = builder.name;
this.nickname = builder.nickname;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static class MemberBuilder {
private Long id;
private String name;
private String nickname;
private int age;
private String phone;
private String address;
public MemberBuilder id(Long id) {
this.id = id;
return this;
}
public MemberBuilder name(String name) {
this.name = name;
return this;
}
public MemberBuilder nickname(String nickname) {
this.nickname = nickname;
return this;
}
public MemberBuilder age(int age) {
this.age = age;
return this;
}
public MemberBuilder phone(String phone) {
this.phone = phone;
return this;
}
public MemberBuilder address(String address) {
this.address = address;
return this;
}
public Member build() {
return new Member(this);
}
}
public static MemberBuilder builder() {
return new MemberBuilder();
}
}
Member member = Member.builder()
.id(1L)
.name("홍길동")
.nickname("길동짱짱맨")
.age(25)
.phone("010-1234-5678")
.address("경북 구미시 ..")
.build();
위 코드에서는 Builder를 활용하여 각 메서드를 통해 입력 파라미터를 설정하고, 메서드 체이닝 방식으로 함수를 실행하며 Member의 멤버 변수를 입력하고 있다.
이를 통해 잘못된 멤버 변수 설정을 조금이라도 더 안전하게 막을 수 있다.
Member.builder()
Member.builder() 메서드를 실행해 MemberBuilder 객체를 생성한다.
id(), name(), nickname(), …
MemberBuilder.id(), MemberBuilder.name(), MemberBuilder.nickname() 메서드를 실행하여 각 매개변수로 입력된 값을 MemberBuilder 멤버변수에 할당한다.
.build()
MemberBuilder.build() 메서드에 Member 생성자에 값이 할당된 MemberBuilder 를 매개변수로 입력한 후 실행한다.
🚀 @Builder
빌더 패턴의 장점은 확실하게 알겠다.
그런데 Member 클래스의 코드가 너무 지저분하다.
이러한 단점을 극복하기 위해 롬복에선 Builder를 알아서 생성해 주는 어노테이션을 지원한다.
public class Member {
private Long id;
private String name;
private String nickname;
private int age;
private String phone;
private String address;
@Builder
private Member(Long id, String name, String nickname, int age, String phone, String address) {
this.id = id;
this.name = name;
this.nickname = nickname;
this.age = age;
this.phone = phone;
this.address = address;
}
}
이런 식으로 생성자 위에 @Builder 어노테이션을 달면, 알아서 빌더 패턴과 관련된 코드를 모두 생성해 준다.
📕 @Builder를 사용하면 생성자가 private이라도 외부에서 접근 가능한 이유
자 오늘의 메인 컨텐츠다.
그런데 위에서 빌더 패턴을 설명하다 보니 이유를 자연스럽게 알게 되었다.
그 이유는 빌더 패턴에서 생성되는 빌더 클래스는 public static 형태로 정의되기 때문이다.
Builder 관련 메서드의 접근 제어자가 public 이고 정적(static)이므로, 해당 클래스 내부에서는 private 생성자에도 접근할 수 있다.
즉, 외부에서는 new 메서드를 호출해 직접 객체를 생성할 수 없지만, 같은 클래스 내부에 있는 빌더 클래스에서는 private 생성자를 호출할 수 있기 때문에 객체를 생성할 수 있는 것이다.
👨🏻💻 참고
'개발 일기' 카테고리의 다른 글
[개발 일기] 2025.02.25 - 전략 패턴 (0) | 2025.02.25 |
---|---|
[개발 일기] 2025.02.24 - == 연산자 vs Objects.isNull() (0) | 2025.02.24 |
[개발 일기] 2025.02.22 - JPA Entity 기본생성자 (0) | 2025.02.22 |
[개발 일기] 2025.02.21 - CDN(콘텐츠 전송 네트워크) (0) | 2025.02.21 |
[개발 일기] 2025.02.20 - 리눅스 Swap Memory (0) | 2025.02.20 |