생성자, 자바빈즈, 빌더 패턴

Ssong94·2022년 10월 11일
0

📌 Java

목록 보기
4/4
post-thumbnail

🌀 Builder Pattern을 고려해보자.


✅ 점층적 생성자 패턴 (Telescoping Constructor Pattern)

필수 매개변수를 받는 생성자를 먼저 생성하고, 선택 매개변수 1개를 추가로 받는 생성자, 선택 매개변수 2개...3개..4개.. 추가로 받는 생성자 등의 형태로 매개변수 개수만큼 생성자를 늘리는 방식이다.
마치 생성자가 점층적으로 성장하는 생성자를 가지도록 한 디장인 패턴이 '점층적 생성자 패턴' 이다.

public class User {
	private final String name;		// 필수
	private final int age;			// 필수
	private final String phone;		// 선택
	private final String address;	// 선택
	
    // #1
	public User(String name, int age, String phone, String address) { ...}
    
    // #2 
    public User(String name, int age) { ...}
    public User(String name, int age, String phone) { ...}
	public User(String name, int age, String phone, String address) { ...} 
}

생성자 패턴에서 USer클래스의 인스턴스를 만드는 법

	// #1 파라미터 인자 값으로 null을 전달한다.
	User user0 = new User("ssong", 28, null, null); 
    
    // #2 원하는 매개변수를 포함한 생성자를 매번 작성해야 한다.
	User user1 = new User("ssong", 28);						
	User user2 = new User("ssong", 28, "010-1234-5678");	
    User user3 = new User("ssong", 28, "010-1234-5678", "서울시 ...");

결론은 생성자 패턴도 쓸 수 있지만, 매개변수 개수가 많아지면 코드를 작성하거나 읽기 어렵다.
그래서 대안으로 쓰는 방법이 JavaBeans Pattern이다.


✅ 자바빈즈 패턴 (JavaBeans Pattern)

매개변수가 없는 생성자로 객체를 만든 후, Setter 메서드를 호출해 매개변수의 값을 설정하는 방식

public class User {
	// non final attribute
	private String name;
	private int age;
	private String phone;
	private String address;
    
    public User() {}
	
	public void setName(String name) { this.name = name; }
	public void setAge(int age) { this.age = age; }
	public void setPhone(String phone) { this.phone = phone; }
	public void setAddress(String address) { this.address = address; }
}

자바빈즈 패턴에서 USer클래스의 인스턴스를 만드는 법

  User user = new User();
  user.setName("ssong");
  user.setAge(28);
  user.setPhone("010-1234-5678");
  user.setAddress("서울시 ...")

생성자 패턴의 단점들이 자바빈즈 패턴에서는 보이지 않는다.
Setter 메서드 때문에 코드가 길어지고 작성하기 번거로울 것 같지만 Generate 또는 lombok의 @Setter를 활용하면 해결된다.

그러나 자바빈즈 또한 단점을 지니고 있다.

  1. 하나의 객체를 완성하려면 메서드를 여러 번 호출해야 한다.
    • 위 객체를 만드는 코드를 보면 Setter메서드가 4개나 호출되었다.
    • 1회의 호출로 객체 생성이 끝나지 않아서 일관성(Consistency)이 깨졌다.
  2. 객체의 불변성을 유지할 수 없다.
    • 객체를 완성한 뒤에도 Setter를 통해 내용을 변경할 수 있다.
    • 대안으로는 Freeze 메서드를 사용하는 방법이 있지만 이 또한 다루기가 어렵고 런타임 오류에 취약해서 권장하지 않는다.

이제 점층적 생성자 패턴과 바자빈즈 패턴에서의 장점만 가져온 Builder 패턴을 알아보자


✅ 빌더 패턴 (Builder Pattern)

빌더 패턴의 UML 다이어그램

빌더 패턴의 정의

빌더 패턴은 복잡한 객체를 생성하는 방법을 정의하는 과정과 표현하는 방법을 정의하는 과정을 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공하는 패턴이다.

클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
마지막으로 매개변수가 없는 build 메서드를 호출해 드디어 우리에게 필요한 객체를 얻는다.

public class User
{
	// final attribute
	private final String name;		// 필수
	private final int age;			// 필수
	private final String phone;		// 선택
	private final String address;	// 선택

	private User(UserBuilder builder) {
		this.name = builder.name;
		this.age = builder.age;
		this.phone = builder.phone;
		this.address = builder.address;
	}

	// Getter만 존재하므로 불변성을 제공함.
	public String getName() { return name; }
	public int getAge() { return age; }
	public String getPhone() { return phone; }
	public String getAddress() { return address; }

	public static class UserBuilder
	{
		private final String name;	// 필수
		private final int age;		// 필수
		private String phone;		// 선택
		private String address;		// 선택

		public UserBuilder(String name, int age) {
			this.name = name;
			this.age = age;
		}
        
		public UserBuilder phone(String phone) {
			this.phone = phone;
			return this;
		}
        
		public UserBuilder address(String address) {
			this.address = address;
			return this;
		}
        
		// 마지막으로 build()를 통해 객체 반환
		public User build() {
			User user =  new User(this);
			validateUserObject(user);
			return user;
		}
        
		private void validateUserObject(User user) {
			// 확인을 위해 기본 유효성 검사를 수행한다.
		}
	}
}

빌더 패턴에서 USer클래스의 인스턴스를 만드는 법

	User user1 = new User.UserBuilder("ssong", 28)
	.build();
    
	User user2 = new User.UserBuilder("ssong", 28)
	.phone("010-1234-5678")
	// no address
	.build();
    
   	User user3 = new User.UserBuilder("ssong", 28)
	.phone("010-1234-5678")
	.address("서울시 ...")
	.build();
  • 위에서 생성한 User객체는 Setter 메소드가 없으므로 한번 빌드되면 상태를 변경할 수 없다. (Immutable한 객체)
  • 코드의 가독성이 매우 좋아진다.
  • 선택적 매개변수를 전달할 필요가 없다. (null 불필요)

단점은 있다...

  • 소스코드의 양이 늘어난다.
    -> lombok의 @Builder를 사용하면 해결이 가능하다.

lombok의 @Builder는 GoF의 Builder Pattern과는 조금 다르다고 하니
lombok 빌더패턴의 Vanilla Java Code는
https://projectlombok.org/features/Builder 에서 확인해보자..


profile
내가 볼라고 씀.

0개의 댓글