티스토리 뷰
728x90
반응형
이전 내용 앞에서 정적 팩토리 메서드를 사용하여 객체를 생성할 때 매개변수가 많을수록 적절히 대응하기 어렵다는 점을 느끼게 되었습니다. 책에서는 여러 매개변수를 받는 생성자를 만들어 놓았지만 저는 불편하여 빌더패턴을 사용했습니다.
점층적 생성자 패턴
💡 예제 코드
- 아래 예제코드처럼 점층적 생성자 패턴을 사용할 수 있지만, 매개변수가 많아지면 생성자를 그만큼 많이 만들어줘야하고 클라이언트에서 객체를 생성하는 시점에 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇 개인지도 주의해서 봐야할 것입니다.
public class User {
private int id;
private String name;
private int age;
private String phoneNumber;
private String address;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
... 추가적인 생성자
}
public class EffectiveJavaApplication {
public static void main(String[] args) {
User user = new User(1, "홍길동", 25);
}
}
자바 빈즈 패턴
💡예제 코드
- setter 메서드를 사용하여 점층적 생성자 패턴의 단점을 보안하였습니다.
- 하지만 자바 빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러개 호출해야하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 됩니다.
- 점층적 생성자 패턴은 매개변수들이 유효한지를 생성자에서만 확인하면 일관성을 유지할 수 있었는데 자바 빈즈는 어디서든 setter를 사용할 수 있으므로 일관성이 무너지고 생각지 못한 버그가 생길 수 있습니다.
@Setter
public class User {
private int id;
private String name;
private int age;
private String phoneNumber;
private String address;
}
public class EffectiveJavaApplication {
public static void main(String[] args) {
User user = new User();
user.setId(1);
user.setName("홍길동");
user.setAge(25);
}
}
빌더 패턴
💡예제 코드
- 클라이언트가 직접 객체를 만드는 대신 필수 매개변수만으로 생성자를 호출하고 build() 메서드를 호출하여 객체를 얻습니다.
public class User {
private int id;
private String name;
private int age;
private String phoneNumber;
private String address;
private User (Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.phoneNumber = builder.phoneNumber;
this.address = builder.address;
}
public static class Builder {
// 필수 매개 변수
private final int id;
private final String name;
private final int age;
// 선택 매개변수 - 기본값으로 초기화
private String phoneNumber = null;
private String address = null;
public Builder(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Builder phoneNumber(String val) {
this.phoneNumber = val;
return this;
}
public Builder address(String val) {
this.address = val;
return this;
}
public User build() {
return new User(this);
}
}
}
public class EffectiveJavaApplication {
public static void main(String[] args) {
User user = new User.Builder(1, "홍길동", 25)
.phoneNumber("010-1234-5678")
.build();
}
}
💡빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다
- 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 공변 반환 타이핑이라 합니다. 이 기능을 사용하면 클라이언트가 형변환에 신경쓰지 않고도 빌더를 사용할 수 있습니다.
public abstract class Pizza {
public enum Topping {
HAM,
MUSHROOM,
ONION,
PEPPER,
SAUSAGE
}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
public class NewYorkPizza extends Pizza {
public enum Size {
SMALL,
MEDIUM,
LARGE
}
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NewYorkPizza build() {
return new NewYorkPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NewYorkPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
public class MexicoPizza extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public MexicoPizza build() {
return new MexicoPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private MexicoPizza(Builder builder) {
super(builder);
this.sauceInside = builder.sauceInside;
}
}
public class EffectiveJavaApplication {
public static void main(String[] args) {
NewYorkPizza newYorkPizza = new NewYorkPizza.Builder(NewYorkPizza.Size.SMALL)
.addTopping(Pizza.Topping.HAM)
.addTopping(Pizza.Topping.PEPPER)
.build();
MexicoPizza mexicoPizza = new MexicoPizza.Builder()
.addTopping(Pizza.Topping.MUSHROOM)
.sauceInside()
.build();
}
}
💡 결론
- 생성자나 정적 팩토리가 처리해야할 매개변수가 많다면 빌더패턴을 사용하는게 좋습니다.
- 매개변수 중 다수가 필수가 아니거나 같은 타입일 경우 더욱 그렇습니다.
- 점층적 생성자보다 코드를 읽고 쓰는데 간결하며 자바빈즈보다 안전합니다.
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item7. 다 쓴 객체 참조를 해제하라. (0) | 2022.07.03 |
---|---|
이펙티브 자바 - Item6. 불필요한 객체 생성을 피하라. (0) | 2022.07.02 |
이펙티브 자바 - Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2022.07.02 |
이펙티브 자바 - Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라. (0) | 2022.07.02 |
이펙티브 자바 - Item1. 객체 생성과 파괴 (0) | 2022.07.02 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- @ControllerAdvice
- pipeline architecture
- 트랜잭셔널 아웃박스 패턴 스프링부트
- 레이어드 아키텍처란
- 람다 표현식
- redis sorted set
- transactional outbox pattern spring boot
- space based architecture
- spring boot redisson 분산락 구현
- java userThread와 DaemonThread
- spring boot poi excel download
- polling publisher spring boot
- 공간 기반 아키텍처
- 자바 백엔드 개발자 추천 도서
- redis sorted set으로 대기열 구현
- pipe and filter architecture
- microkernel architecture
- spring boot excel download oom
- spring boot redis 대기열 구현
- service based architecture
- spring boot redisson sorted set
- spring boot redisson destributed lock
- redis 대기열 구현
- transactional outbox pattern
- java ThreadLocal
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- JDK Dynamic Proxy와 CGLIB의 차이
- spring boot 엑셀 다운로드
- 서비스 기반 아키텍처
- spring boot excel download paging
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함