빌더패턴

신동혁·2022년 8월 12일
0

디자인패턴

목록 보기
4/5

1. 빌더 패턴 필요성( Build Pattern )

빌더 패턴은 생성자를 대체하기 위한 방법이다. 생성자의 어떤 불편한 점을 보완한 것일까? 바로 멤버변수를 파라미터로 받는 부분이다. 간단한 예시를 들어본다.

1.1 빌더패턴이 필요한 이유( 문제상황 )

1) 클래스A는 멤버변수 name,age,gender 3가지를 가지고 있다.

public class A {
    public String name;
    public int age;
    public String gender;
}

2) 이때 클래스A 생성자를 통해 name만 값을 부여해 클래스A 객체를 만들고 싶다면?

  • 방법1 : AllArgsConstructor에 name 이외 파라미터값을 null이나 0같은 쓰레기값을 넣어주며 생성자를 이용한다.
A A객체명 = new A("넣어줄이름",0,null);
  • 방법2 : name만 파라미터로 받는 생서자를 따로 만들어준다.
public class A {
    public String name;
    public int age;
    public String gender;

    public A(String name) {  // name만 파라미터로 받는 생성자 생성
        this.name=name;
    }
}

3) 이렇게 멤버변수값을 넣을 때도 있고 안 넣을 때도 있다면 굉장히 불편한 상황이 생길 수 있다.

  • 예를 들어 멤버면수가 50개이고 여기서 초기화해줄 변수는 1개만 있다면, 굳이 1개의 값과 49개의 쓰레기값을 같이 작성해주어야 한다.
  • 또 맴버변수를 2개만 넣을 때, 3개만 넣을 때 등등 초기화해줄 멤버변수의 종류와 개수가 달라질 경우 그 조건에 맞는 생성자를 계속 만들어줘야 하는 문제점이 발생한다.
  • 이 밖에도 클래스가 수정되어 멤버변수가 추가된다면 예전 생성자 형식을 이용한 코드 부분들을 모두 수정해줘야 하는 번거로움이 생길 수 있다.

이런 상황들로 인해 처음부터 원하는 멤버변수만을 파라미터로 받아서 초기화를 시킬 수 있는 생성자를 만들었고, 이것이 바로 빌더(Builder)다.

1.2 다른 해결법과 비교( 자바빈즈 패턴 )

굳이 빌더를 쓰지 않더라도 위 문제들을 해결할 수는 있다.
1. 기본 생성자를 이용해 객체를 만든다.
2. setter를 이용해 원하는 멤버변수만을 초기화한다.
이런 방법을 자바빈즈 패턴이라고도 부른다.

위에서 제시한 3가지 상황에 대입해 본다.

  • 예를 들어 멤버면수가 50개이고 여기서 초기화해줄 변수는 1개만 있다면, 굳이 1개의 값과 49개의 쓰레기값을 같이 작성해주어야 한다.
    -> 기본 생성자를 이용해 객체 만든 뒤, setter메서드 1번만 호출하면 끝.
  • 또 맴버변수를 2개만 넣을 때, 3개만 넣을 때 등등 초기화해줄 멤버변수의 종류와 개수가 달라질 경우 그 조건에 맞는 생성자를 계속 만들어줘야 하는 문제점이 발생한다.
    -> 기본 생성자를 이용해 객체 만든 뒤, 초기화 하고 싶은 멤버변수만 골라서 setter메서드 호출하면 끝.
  • 이 밖에도 클래스가 수정되어 멤버변수가 추가된다면 예전 생성자 형식을 이용한 코드 부분들을 모두 수정해줘야 하는 번거로움이 생길 수 있다.
    -> 기본 생성자를 이용했기에 전혀 문제없음. 심지어 이미 생성된 객체에 추가된 멤버변수값을 초기화하고 싶으면 setter메서드 호출로 가능하다.

문제점 :
1. 초기화할 멤버변수가 많다면 setter를 여러번 호출해야 한다. 만약 setter 작업들이 아직 끝나지 않았는데 누군가 미완성된 객체를 호출해버린다면? 문제발생! 그러므로 스레드 안정성을 위해 추가작업이 필요하게 된다.
2. Open-Closed법칙에 위배된다. 무슨 뜻이냐면 불필요하게 확장 가능성을 열어두었다는 뜻이다. 클래스의 변수는 변경을 많이 하면 할수록 유지보수가 힘들어지게 된다. 언제 값이 바뀌었는지 같은 부분 체크가 힘들기 때문이다. 하지만 setter를 이용하면 언제나 값이 수정가능해져 오히려 유지보수를 힘들게 하는 메서드가 되버릴 수 있다.

2. 빌더 패턴 구성

Builder패턴을 3파트로 나누어 만들어보았다. 구성은 다음과 같다.

  • AllArgsConstructor : builder클래스 마지막에 상위클래스 타입으로 반환해주는 기능을 위해 상위클래스 타입 생성자를 생성해준다
  • builder클래스 객체 생성,반환하는 메서드 : 클래스의 객체 대신 builder클래스의 builder객체를 만들어주는 형식이다
  • builder클래스 : 상위클래스와 같은 멤버변수를 가지고 있으므로 수정 이후 상위클래스 타입으로 반환해준다
public class ProbonoProjectDTO {
	private int probonoProjectId;
	private String probonoProjectName;
	private String probonoId;
	private String activistId; 
	private String receiveId;
	private String projectContent;

    //Builder직접 만들어보기
	//1.AllArgsConstructor만들어주기
	ProbonoProjectDTO(int probonoProjectId,
						String probonoProjectName,
						String probonoId,
						String activistId,
						String receiveId,
						String projectContent) {
		this.probonoProjectId=probonoProjectId;
		this.probonoProjectName=probonoProjectName;
		this.probonoId=probonoId;
		this.activistId=activistId;
		this.receiveId=receiveId;
		this.projectContent=projectContent;
	}
	
	//2.Builder클래스 생성,반환하는 builder메서드 만들어주기
	public static ProbonoProjectDTOBuilder builder() {
		return new ProbonoProjectDTOBuilder();
	}
	
	//3.Builder클래스 만들어주기
	public static class ProbonoProjectDTOBuilder{
		private int probonoProjectId;
		private String probonoProjectName;
		private String probonoId;
		private String activistId; 
		private String receiveId;
		private String projectContent;
		
		ProbonoProjectDTOBuilder(){
			
		}
		
		public ProbonoProjectDTOBuilder probonoProjectId(int probonoProjectId) {
			this.probonoProjectId=probonoProjectId;
			return this;
		}
		public ProbonoProjectDTOBuilder probonoProjectName(String probonoProjectName) {
			this.probonoProjectName=probonoProjectName;
			return this;
		}
		public ProbonoProjectDTOBuilder probonoId(String probonoId) {
			this.probonoId=probonoId;
			return this;
		}
		public ProbonoProjectDTOBuilder activistId(String activistId) {
			this.activistId=activistId;
			return this;
		}
		public ProbonoProjectDTOBuilder receiveId(String receiveId) {
			this.receiveId=receiveId;
			return this;
		}
		public ProbonoProjectDTOBuilder projectContent(String projectContent) {
			this.projectContent=projectContent;
			return this;
		}
		
		public ProbonoProjectDTO build() {
			return new ProbonoProjectDTO(this.probonoProjectId
					, this.probonoProjectName
					, this.probonoId
					, this.activistId
					, this.receiveId
					, this.projectContent);
		}
		
		public String toString() {
			return "Builder구성 : "
					+ "DTO명 : ProbonoProjectDTO"
					+ " 프로젝트명 : " + this.probonoProjectName
					+ " 프로젝트ID : " +this.probonoId
					+ " 기부자 : " +this.activistId
					+ " 수혜자 : " +this.receiveId
					+ " 내용 : " +this.projectContent;
		}
	}
}

전체적인 흐름을 보면 클래스ProbonoProjectDTO와 같은 형식의 멤버변수를 가진 클래스ProbonoProjectDTOBuilder를 만들어 이 클래스의 객체를 만들고 멤버변수를 초기화해 다시 클래스ProbonoProjectDTO 타입으로 반환해준다. 마찬가지로 위에서 언급한 3가지 상황에 대입하여 어떻게 문제를 해결했는지 알아본다.

  • 예를 들어 멤버면수가 50개이고 여기서 초기화해줄 변수는 1개만 있다면, 굳이 1개의 값과 49개의 쓰레기값을 같이 작성해주어야 한다.
    -> ProbonoProjectDTO 객체명 = ProbonoProjectDTO.builder().probonoProjectId(100).build();
    다음과 같은 식을 이용하면 probonoProjectId 멤버변수 하나만을 초기화할 수 있다.
  • 또 맴버변수를 2개만 넣을 때, 3개만 넣을 때 등등 초기화해줄 멤버변수의 종류와 개수가 달라질 경우 그 조건에 맞는 생성자를 계속 만들어줘야 하는 문제점이 발생한다.
    -> ProbonoProjectDTO 객체명 = ProbonoProjectDTO.builder().probonoProjectId(rset.getInt(1)).probonoProjectName(rset.getString(2).build();
    위와 같은 원리로 초기화할 멤버변수에 관한 메서드만 연속해 호출하면 원하는 멤버변수 값만 초기화하면서 객체 생성이 가능하다.
  • 이 밖에도 클래스가 수정되어 멤버변수가 추가된다면 예전 생성자 형식을 이용한 코드 부분들을 모두 수정해줘야 하는 번거로움이 생길 수 있다.
    -> 문제없음. 추가된 멤버변수를 초기화하고 싶다면 추가된 멤버변수 초기화메서드를 이어붙여 호출하면된다.

3. 자바 롬복 빌더

자바에서는 롬복(lombok)을 통해 간단하게 어노테이션(@Builder)으로 빌더를 생성할 수 있다.

3.1 자바 롬복 빌더 주의할 점

만약 클래스 멤버변수를 선언하고 값을 초기화해주었다면, 이는 롬복 빌더에서 적용되지 않는다. 예제를 본다.

public class Member {
	private int age=999;
}

Member클래스에 멤머변수 age를 999 int값으로 초기화했다. 이 클래스에 @Builder를 사용해 빌더를 만든 후, age값을 설정하지 않고 Member객체를 빌드한다면? Member클래스에 toString을 설정해주고 출력해본다.

Member member123 = Member.builder().build();
System.out.println(member123);
// 출력화면 : Member [age=0]

age는 분명 999로 초기화했지만 0으로 출력이 된다. 왜 그럴까?

이유는 롬복 빌더의 Builder클래스 생성 방식때문일 것이다. Builder클래스를 선언할 때 Membr클래스의 멤버변수를 초기화한 것처럼 똑같이 멤버면수를 선언하고 초기화까지 한다면 이런 상황을 발생하지 않을 것이다. 하지만 Builder클래스는 단지 Member클래스의 멤버변수를 똑같이 선언만하지 초기화는 하지 않는다. 이로 인해 초기화값에 대한 내용은 롬복 빌더를 사용했을 때 적용되지 않는다.(초기화값 대신 0,null같은 더미값이 들어감)

내 생각( 마무리 )

내 생각으로는 setter를 이용한 자바빈즈 패턴과 거의 흡사하다고 생각된다. 다른점은 멤버변수를 초기화하는 메서드를 이어붙여 한번에 호출 가능하다는 점이다.

  • 자바빈즈 패턴
클래스명 객체명 = new 클래스명();
객체명.set멤버변수1(1);
객체명.set멤버변수2(2);
객체명.set멤버변수3(3);
객체명.set멤버변수4(4);
  • 빌더 패턴
클래스명 객체명 = 클래스명.builder().멤버변수1메서드(1).멤버변수2메서드(2).멤버변수3메서드(3).멤버변수4메서드(4).build();

내가 이해한 빌더 요약 : 자바빈즈 패턴처럼 객체를 생성하고 setter로 멤버변수를 초기화하는 모든 과정을 클래스 안에 집어넣어 하나의 작업을 묶은 패턴

profile
개발취준생

0개의 댓글