목차
객체를 생성하는 방법에는 3가지 패턴이 있다.
점층적 생성자 패턴 : 파라미터 별로 생성자를 만든다
public class Member {
private Long id;
private String name;
private int age;
private Team team;
public Member(Long id) {
this.id = id;
}
public Member(Long id, String name) {
this.id = id;
this.name = name;
}
public Member(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Member(Long id, String name, int age, Team team) {
this.id = id;
this.name = name;
this.age = age;
this.team = team;
}
}
Member member1 = new Member("1");
Member member2 = new Member("2", "AA");
Member member3 = new Member("3", "AA", 20);
Member member4 = new Member("4", "AA", 30, teamA);
단점
: 객체를 생성할 때, 파라미터 순서가 중요하기때문에 순서를 알고있어야 값을 넣을 수 있다.
: member가 생성되는건 알겠지만 어떤 필드에 어떤 값이 들어가는지는 코드를 보고 알 수 없다. (Member클래스를 봐야지만 이해할 수 있다)
: 파라미터의 조합 만큼 생성자를 정의해야한다
자바빈 패턴 : 빈 생성자( )와 Getter & Setter를 생성한다
public class Member {
private Long id; //선택
private String name; //필수
private int age; //필수
private Team team; //필수
public Member() {
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Team getTeam() { return team; }
public void setTeam(Team team) { this.team = team; }
}
Member member1 = new Member();
member1.setTeam(teamA);
member1.setName("AA");
member1.setAge(20);
Member member2 = new Member();
member2.setTeam(teamB);
member2.setAge(30);
개선
: 객체 생성 시 어떤 필드에 어떤 값을 넣는지 명시되어 코드 이해가 빠르다
: 파라미터 순서에 상관없이 값을 넣을 수 있다
단점
: 빈 생성자( )만 만들고 setter를 호출하지 않아도 컴파일러에서 오류로 잡히지 않는다!!
: Member가 값이 모두 필요한 객체라면.. 갑자기 어디선가NullPointerException이 발생될 수 있다
: setter가 호출되기 전까지는 객체의 일관성이 깨져버린다.
: 필수로 들어가야하는 필드를 알고있어야 setter로 값을 넣어줄 수 있다.
Builder 빌더 패턴 - 1 : 필수 필드를 생성자로
점층적 생성자 패턴의 장점은 안정성, 자바빈 패턴의 장점은 가독성이였다.
필수 매개변수는 생성자로 안정성을 높이고, 선택 매개변수는 setter로 설정하여 가독성을 높인 패턴이 빌더패턴이다.
public class Member {
private Long id; //선택
private String name; //필수
private int age; //필수
private Team team; //필수
//private Member(Builder builder) {
// this.id = builder.id;
// this.name = builder.name;
// this.age = builder.age;
// this.team = builder.team;
//}
public static class Builder{
private Long id; //선택
private String name; //필수
private int age; //필수
private Team team; //필수
//필수 필드 생성자로 생성
public Builder(String name, int age, Team team) {
this.name = name;
this.age = age;
this.team = team;
}
//선택 필드 setter 생성 후 void를 Builder로 변경
//return this; 추가
public Builder setId(Long id) {
this.id = id;
return this; //체이닝 가능해짐
}
public Member build(){
Member member = new Member();
member.id = this.id;
member.name = this.name;
member.age = this.age;
member.team = this.team;
return member;
//return new Member(this);
//이렇게 넘긴 후 Member에 주석으로 된 private 생성자를 만드는 방법도 있다
}
}
}
Member member1 = new Member.Builder("hello", 20, teamA).build();
Member member2 = new Member.Builder("world", 10, teamA).setId(2L).build();
개선
: 필수 필드의 경우엔 값을 보장받을 수 있다. 필수 필드에 값이 없다면 컴파일러에서 오류로 잡힌다
: 선택 필드의 경우엔 순서에 상관없이 값을 넣을 수 있다
단점
: Member처럼 필수 필드가 많은 경우엔 생성자를 사용하니 가독성이 떨어진다 ( = 선택 필드가 많을 때 사용하면 유용하다!)
Builder 빌더 패턴 - 2 : build() 시 필수 필드가 없다면 예외처리
public class Member {
private Long id; //선택
private String name; //필수
private int age; //필수
private Team team; //필수
public static class Builder{
private Long id; //선택
private String name; //필수
//private int age; //필수 int는 값이 안들어와도 기본값이 0이다. null 체크를 위해 Integer를 사용하자
private Integer age; //필수
private Team team; //필수
//필수 필드 생성자로 생성
public Builder(String name, Integer age, Team team) {
this.name = name;
this.age = age;
this.team = team;
}
//선택 필드 setter 생성 후 void를 Builder로 변경
//return this; 추가
public Builder setId(Long id) {
this.id = id;
return this; //체이닝 가능해짐
}
public Member build(){
//필수 필드에 값이 없다면 예외발생
if(name == null || age == null || team == null){
throw new IllegalArgumentException("Member의 필수값이 할당되지 않았습니다.");
}
//Assert를 이용하는 방법도 있다
//Assert.notNull(name, "Member의 name이 null입니다.");
//Assert.notNull(age, "Member의 age가 null입니다.");
//Assert.notNull(team, "Member의 team이 null입니다.");
Member member = new Member();
member.id = this.id;
member.name = this.name;
member.age = this.age;
member.team = this.team;
return member;
}
}
}
Member member1 = new Member.Builder()
.setAge(20)
.setName("hello")
.setTeam(teamA)
.build();
Member member2 = new Member.Builder()
.setName("world")
.setAge(15)
.setTeam(teamB)
.setId(3L)
.build();
개선
: 필수 필드도 순서에 상관없이 값을 넣을 수 있다.
: 예외처리로 값을 보장받을 수 있다. 필수 필드에 값이 없다면 컴파일러에서 오류로 잡힌다
단점
: 프로그램을 실행해봐야만 필수 필드에 값이 있는지 확인할 수 있다 (없다면 예외가 발생될것이니..)
Lombok의 @Builder 어노테이션 : Builder를 편하게 써보자
해당 어노테이션을 사용하면 바이트코드를 조작해서 Builder패턴을 알아서 만들어주는데,
클래스에 붙일때와 생성자에 붙일때의 동작방식이 다르다.
@Builder를 클래스에 붙인 경우
@Builder를 클래스에 붙이면 모든 필드의 생성자가 추가된다.
Member member1 = Member.builder()
.age(20)
.name("hello")
.team(teamA)
.build();
//주의! new 키워드가 없다
=> @Builder를 클래스에 붙이면 사실 setter를 사용하는 것과 다르지 않다
=> 객체를 생성할때 연관으로 뜨는 메서드들중에 어떤 것이 필수인지 모르게된다
@Builder를 적용하는 클래스에 커스텀하게 정의한 생성자가 있다면 @AllArgsConstructor를 사용해야한다
@Builder
@AllArgsConstructor //커스텀하게 정의한 생성자가 있다면 @AllArgsConstructor를 붙여주어야
//@Builder와 함께 사용가능
public class Member {
private Long id;
private String name;
private int age;
private Team team;
public Member() {
}
public Member(String name, int age, Team team) {
this.name = name;
this.age = age;
this.team = team;
}
}
만약 내가 정의한 생성자가 있는 경우, @AllArgsConstructor를 붙여주어야 에러가 발생하지 않는다.
@Builder를 생성자에 붙인 경우
@Builder를 생성자에 붙이면 지정된 생성자대로 Builder패턴이 만들어진다.
클래스에 붙인것과 다르게 @AllArgsConstructor가 추가되지 않는다!
Member member1 = Member.builder()
.age(20)
.name("hello")
.team(teamA)
.build();
//주의! new 키워드가 없다
=> 객체 생성 시 선택 필드에 관한 메서드는 더이상 나오지 않게되었다!
=> 꼭 필요한 필수 필드에 대해서만 메서드가 나오게 되었지만 아직도 setter를 사용하는 것과 같다
=> name이 없어도 Member는 생성된다.
@Builder 필수 파라미터 보장
예외발생
public class Member {
private Long id;
private String name;
private int age;
private Team team;
@Builder
public Member(String name, int age, Team team) {
Assert.notNull(name, "name 없어요");
Assert.notNull(age, "age 없어요");
Assert.notNull(team, "team 없어요");
this.name = name;
this.age = age;
this.team = team;
}
}
Member member1 = Member.builder()
.age(20)
.team(teamA)
.build();
//실행하면 에러 발생
위에처럼 커스텀 생성자에 Assert문, 또는 If문으로 예외를 발생시켜서 필수 파라미터의 값을 보장하는 방법이 있다
=> 파라미터 순서에 상관없이 값을 넣을 수 있다.
=> 하지만 해당 메서드를 실행할때까지는 에러가 있는지 알 수 없다
builder() 미리 정의
public class Member {
private Long id;
private String name;
private int age;
private Team team;
@Builder
public Member(String name, int age, Team team) {
this.name = name;
this.age = age;
this.team = team;
}
public static Member.MemberBuilder builder(String name, Integer age) {
MemberBuilder memberBuilder = new MemberBuilder();
memberBuilder.name(name);
memberBuilder.age(age);
return memberBuilder;
}
}
Member member2 = Member.builder("hello", 20).team(teamA).build();
lombok이 만들어주는 builder()를 대신 미리 작성해준다! 그러면 lombok이 builder를 다시 만들지 않고 작성한대로 사용된다.
=> 실행 전에 컴파일 오류가 발생하게끔 되었다
=> 하지만 생성자처럼 파라미터의 순서가 중요해졌다..
@Builder를 여러개 작성하여 역할을 구분해주자
@Builder(builderMethodName = "") : builder 메소드명을 재정의함
@Builder(builderClassName = "") : builder 클래스명을 정의함
builderMethodName만 정의했을 때
=> MemberBuilder 내부 클래스가 서로 공유되고있다
=> caseDelete에는 team이 없어야하는데 team까지 나오고있다
builderClassName과 builderMethodName을 함께 사용했을 때
=> 클래스명을 지정해준대로 다른 inner 클래스가 생성되었다
caseDelete에 team이 더이상 나오지 않게 되었다
builderClassName과 builderMethodName을 함께 사용했을 때 필수값을 지정하고 싶다면
public class Member {
private Long id;
private String name;
private Integer age;
private Team team;
// builder를 미리 작성해주고자 하면 ClassName과 MethodName에 대소문자 구분해서 작성해주자
@Builder(builderClassName = "CaseUpdate", builderMethodName = "caseUpdate")
public Member(String name, Integer age, Team team) {
this.name = name;
this.age = age;
this.team = team;
}
//lombok이 만들어줄 builder를 먼저 만들어준다
//컴파일 에러로 잡을 수 있다. 하지만 매개변수의 순서가 중요해진다
public static Member.CaseUpdate caseUpdate(String name, Integer age){
CaseUpdate caseUpdate = new CaseUpdate();
caseUpdate.name(name);
caseUpdate.age(age);
return caseUpdate;
}
@Builder(builderClassName = "CaseDelete", builderMethodName = "caseDelete")
public Member(String name, Integer age) {
//예외 발생하게 처리
//컴파일 에러로는 잡을 수 없다. 실행해야만 에러를 찾을 수 있다.
Assert.notNull(name, "name 없어요");
Assert.notNull(age, "age 없어요");
this.name = name;
this.age = age;
}
}
Member member1 = Member.caseUpdate("hello", 20).team(teamA).build();
Member member2 = Member.caseDelete().name("world").build(); //에러 발생할 것
@Builder를 사용할 때 주의할 점 정리
1. @Builder 사용 위치?
되도록 생성자에 @Builder를 사용하자.
2. @Builder 필수 파라미터를 보장해주고 싶다면?
- Assert나 If문으로 예외를 발생시키자. 실행 후에만 예외가 발생함을 알 수 있다.
- lombok이 만들어줄 builder를 미리 만들어주자. 컴파일 에러로 찾을 수 있지만 매개변수의 순서가 중요해진다
3. @Builder를 여러개 사용하고 싶다면?
builderClassName과 builderMethodName을 반드시 지정해주자!
lombok으로 편리하게 사용할수는 있지만..
builder패턴은 직접 만들어주는 것도 크게 나쁘지 않다고 생각한다!
'Computer Science' 카테고리의 다른 글
프로세스? 스레드? (0) | 2022.02.17 |
---|---|
동적계획법 DP 구현방법 ( + [백준] 11050 이항계수 문제풀이) (0) | 2022.02.13 |
DFS, BFS 구현방법 ( + [백준] 2606 바이러스 문제풀이) (0) | 2022.02.12 |
[JWT] JSON Web Token의 구조, 장점, 한계 (0) | 2022.02.03 |
정보보안의 3대 요소(기밀성, 무결성, 가용성)와 RSA 암호화 방식 (0) | 2022.02.02 |