티스토리 뷰
클론코딩을 하거나, 인강을 듣거나, 테스트케이스로 알맞은 로직인지 확인할 때 Builder패턴을 많이 사용하는 것을 봤다.
언뜻보면 생성자와 다른 점이 보이지 않았고, 필드 값이 적으면 Setter메서드를 사용하면 되는데 왜 Builder패턴을 강조하면서 이야기 할까? 그래서 Builder패턴에 대해서 알아보았다.
1. 자바빈즈 패턴
우리는 대부분 개발을 할 때, 객체 생성 후 내부 변수 세팅을 위해서 Setter메서드를 호출하는 방법을 쓰고 있다.
setter 메서드로 객체 필드 값을 초기화 하는 방법을 자바빈즈 패턴 이라고 한다.
그런데 변수 갯수가 많은 경우는 어떨까 ?
아래코드는 회원의 정보를 나타내는 클래스와 setter로 객체를 초기화 시키는 방법이다.
@Data
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String student_name;
private int age;
private int class_number
private String email;
private String school;
private String teacher;
private int birthday;
}
Member member = new Member();
member.setStudentName("익명");
member.setAge(18);
member.setClassNumber(2206);
member.setEmail("test@gmail.com");
member.setSchool("서울대학교");
member.setTeacher("홍길동");
member.setBirthday(20050729);
지금도 7개의 멤버변수(필드)를 변수 세팅을 위해 일일히 넣기가 귀찮다. 7개도 귀찮은데 10개, 20개, ... 50개의 멤버변수를 세팅해주어야 할 때는 "과연 이게 옳은 방법일까 ?" 라고 생각이 날 것이다.
2. setter 사용의 지양
거의 모든 개발자들은 setter 사용을 지양해야 한다고 말한다. 물론 상황에 따라 setter 메서드로 필드 값을 넣는 것이 편할 때가 있다. (혼자 개발할 때, 필드의 개수가 적을 때) 아마 이 두 조건을 모두 만족한다면 setter 메서드를 써도 뭐라 할 사람은 없을 것이다.
그러면 왜 setter 사용을 지양해야 할까 ?
setter 사용을 지양해야하는 이유는 여러가지가 있지만, 가장 큰 두 가지 이유를 뽑자면 setter를 사용하면 값을 변경한 의도를 파악하기가 힘들고 객체의 일관성을 유지하기 위한다는 점이다.
2-1. 의도를 파악하기 힘듬
코드로 예시를 들어보겠다.
member.setFistName("value");
member.setLastName("value");
member.setAge("value");
단순히 setter로 값을 변경한 코드다. 코드를 짰을 때에는 어떤 의도와 목적을 가지고 값을 변경하였는지 알고있을 것이다.
하지만 시간이 지나고 다시 위 코드를 봤을 땐 왜 값을 변경하게 되었는지 알려면 단순히 생각해내는 것 말고는 코드를 역추적해서 찾을 수 밖에 없다. 그렇게 되면 아까운 시간만 낭비되는 것뿐만 아니라 가독성이 떨어지고 무분별한 setter로 인해 코드 줄 만 차지하고 있을 것이다.
2-2. 객체의 일관성을 유지하기 위함
만약 회원의 이름을 변경하는 메서드를 만들었다고 가정해보자. 그런데 다른 코드를 짜다가 어쩔 수 없이 회원의 이름을 변경해야할 때가 오면 무작정 setter로 값을 바꾸게 될 것이다. 그렇게 되면 문제점이 발생하게 된다.
회원의 이름을 변경하는 메서드가 의미가 없게 된다. 즉, 회원의 이름을 다른 곳에서 변경할 수 있게 되어 코드의 기능이 꼬이게 되는 것이 (한마디로 객체의 일관성을 유지하기가 어려움)
하지만 메서드의 이름에 의도가 담겨져있어 가독성이 뛰어나다는 장점도 있다.
3. 생성자 사용
그렇다면 setter를 대신할 방법은 뭐가 있을까 ? 바로 생성자를 이용해서 객체를 초기화 해주는 방법이다.
위에서 예를 들었던 회원 정보 클래스를 생성자를 사용해보자.
@Data
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String student_name;
private int age;
private int class_number
private String email;
private String school;
private String teacher;
private int birthday;
public Member(String student_name, int age, int class_number, String email, String school, String teacher, int birthday) {
this.student_name = student_name;
this.age = age;
this.class_number = class_number;
this.email = email;
this.school = school;
this.teacher = teacher;
this.birthday = birthday;
}
}
Member = member = new Member("익명", 18, 2206, "test@gmail.com", "서울대학교", "홍길동", 20050729);
클래스의 코드길이는 늘어났지만 객체를 초기화 해주는 방법은 단 한줄로 끝났다. 위에 setter로 가득 채우는 것보다 훨씬 코드길이가 줄어들었다. 하지만 생성자를 사용했을 때의 단점이 존재한다.
3-1. 매개변수의 따른 대응이 어렵다.
A, B, C매개변수가 있다고 가정해보자. A, B, C를 모두 초기화 해주는 경우와 A, C만 초기화 해줘야하는 경우도 생길 수 있다.
바로 필수인자가 필요할 경우다.
예를 들어 회원가입을 할 때 이메일을 선택사항으로 둔다고 가정해보면 이메일을 제외한 나머지 필드를 초기화를 시켜주면 된다.
그렇다면 새롭게 생성자를 만들어 주어야 한다. 이렇게 상황에 따라 계속해서 생성자를 만들다보면 코드가 혼잡해질 수 있다.
3-2. 가독성이 떨어진다.
setter보단 코드길이가 단축되지만, setter에 비해 가독성이 현저히 떨어진다. 아래코드를 예시로 들어보겠습니다.
Member member = new Member(12, 3, 6, 8, 280, 10);
만약 각각의 매개변수가 무엇을 의미하는지 까먹거나, 리뷰어가 이 코드를 볼 때
정확한 위치에 정확한 값을 넣었는지 확인하고, 빠진 매개변수는 없는지 일일이 확인해야하는 번거로움이 생긴다.
호출코드만 보고서는 인자가 무슨 값인지 알 수 가 없다. 특히 위 코드와 같이 자료형이 같은 인자들이 많을 경우 컴파일 단계에서 걸러지지 않아 런타임시 문제가 발생하기 딱 좋다. (실수하기 좋은 코드가 완성된 것)
4. Builder 패턴
빌더패턴은 생성자의 안정성(생성자의 장점)과 자바빈즈의 가독성(setter의 장점)을 다 가진 패턴이라 볼 수 있다.
빌더패턴을 제대로 사용하기 위한 3가지 내용이 있다.
1. 빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어두는 것이 일반적이다.
2. 빌더 클래스의 생성자는 Public으로 선언한다.
3. 필수 변수, 선택 변수를 나누고 선택변수의 경우 초기화 시켜준다.
아래코드로 예시를 들어보자.
public class NutritionFacts {
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public static class Builder {
//필수인자
private int servingSize;
private int servings;
//선택인자는 초기화
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
//기본 생성자
public Builder() {}
//선택은 안적을 수 있어도 필수는 적어야 하기 때문
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this; //이렇게 하면 .으로 이어갈 수 있음
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts
.Builder(240, 8) //필수값 입력
.calories(100)
.sodium(35)
.carbohydrate(27)
.build(); //객체를 생성해 돌려줌
필드를 초기화 하는 코드를 보면 매개변수가 무엇을 의미하는 지 알 수 있기 때문에 가독성이 좋아졌다.
하지만 클래스 쪽은 뭔가 더 어려워진 느낌이다. 물론 개발자들은 위 코드처럼 장황하게 쓰지 않는다.
스프링에선 lombok의 자동으로 위 코드로 만들어주는 @Builder 가 있다.
public class NutritionFacts {
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
@Builder
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
빌더패턴을 사용하게 되면 인자 값의 순서가 상관없다. 또한 상황에 맞게 유연한 코드가 완성된다.
예를 들어 3개의 인자가 필요할 때, 생성자는 따로 3개의 인자를 받는 생성자를 만들어줬어야 했지만, 빌더는 3개의 인자만 넣으면 된다.
즉, 3개의 인자를 가지는 형태로 변형된다. 이로인해 유지보수가 향상된다.
'SpringBoot' 카테고리의 다른 글
자주 사용하는 Thymeleaf 문법 (0) | 2022.07.08 |
---|---|
게시판 화면을 위한 Thymeleaf 적용과 데이터 출력하기 (0) | 2022.07.08 |
자바 코드로 직접 스프링 빈 등록하기 (0) | 2022.04.11 |
컴퍼넌트 스캔으로 자동 의존관계 설정 (0) | 2022.04.09 |
Java Bean & Spring Bean (0) | 2022.04.04 |
- Total
- Today
- Yesterday