빌더 패턴(Builder Pattern)은 복잡한 객체의 생성을 단순화하고, 가독성을 높이며, 불변성을 유지하는 데 유용한 디자인 패턴입니다. 이 패턴을 사용하면 객체의 속성을 단계적으로 설정할 수 있어 가독성이 좋고 유지보수가 용이합니다.
 

빌더 패턴이 필요한 이유

1. 생성자 오버로딩 문제 :

객체를 생성할 때 생성자 오버로딩을 활용하면 다양한 매개변수 조합을 처리할 수 있습니다. 하지만 매개변수 개수가 많아질수록 생성자 오버로딩이 복잡해지고 유지보수가 어려워집니다.
 

1) 생성자 오버로딩(Constructor Overloading)이란? : 
클래스 내에서 같은 이름의 생성자를 여러 개 정의하는 것을 의미합니다.
즉, 매개변수의 개수나 타입이 다르게 여러 개의 생성자를 만드는 기법입니다.
생성자 오버로딩은 객체를 다양한 방법으로 초기화할 수 있어 유연성을 제공하지만,
매개변수의 개수가 많아질 경우 코드 가독성과 유지보수성이 떨어지는 문제가 발생할 수 있습니다.

public class User {
    private final String name;
    private final int age;

	// 생성자 1: 이름만 설정 (age 기본값 0)
    public User(String name) {//매개변수 name 
        this.name = name;
        this.age = 0; 
    }
	
    //필드가 많아질수록 경우의 수가 기하급수적으로 증가하여 생성자 관리가 어려워짐
    //public User(String name, int age, String email, String address) { ... }
    //public User(String name, int age, String email) { ... }
    //public User(String name, String email) { ... }
    //public User(String name, int age) { ... }

    public void displayInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

 

2. 가독성 및 유지보수

 
생성자의 매개변수가 많으면 어떤 값이 어떤 속성을 의미하는지 알아보기 어렵습니다.

public class Main {
    public static void main(String[] args) {
    //"Alice"는 이름(name)이고 25는 나이(age)이지만, 명확하게 이해하기 어렵습니다.
    User user = new User("Alice", 25);
         user.displayInfo(); // 출력: 이름: Alice, 나이: 25
    }
}

 
빌더 패턴을 사용하면 아래 코드와 같이 가독성이 개선됩니다.

 public static void main(String[] args) {
	//setName()과 setAge()를 사용하여 어떤 값을 설정하는지 명확하게 알 수 있습니다.
	User user = new User.UserBuilder()
            	.setName("Alice")
            	.setAge(25)
            	.build();
                
        user.displayInfo(); // 출력: 이름: Alice, 나이: 25
 }

 

3. 빌더패턴 적용

**빌더 패턴(Builder Pattern)**을 사용하면 생성자 오버로딩 문제를 해결할 수 있습니다.
빌더 패턴을 사용하면 유지보수가 쉬워지고, 가독성이 향상되며, 불변 객체를 만들기 용이합니다.

public class User {
    // **필드 (불변 객체를 위해 final 사용)**
    private final String name;
    private final int age;
	 
    // **🔹 private 생성자** : 외부에서 직접 객체 생성을 막고, 빌더를 통해서만 생성할 수 있도록 제한
    private User(UserBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

	// **🔹 내부 정적 클래스 : UserBuilder (User 객체 생성을 위한 빌더)**
    public static class UserBuilder {
        private String name;
        private int age;
		
        //**🔹 setName() 메서드** : name 필드를 설정하고 현재 빌더 객체(this)를 반환하여 메서드 체이닝 가능
        public UserBuilder setName(String name) { 
            this.name = name;
            return this;
        }

        public UserBuilder setAge(int age) { 
            this.age = age;
            return this;
        }
        
        // **🔹 build() 메서드** : 설정된 값으로 User 객체를 생성하여 반환
        public User build() {
            return new User(this);
        }
    }
    
     public void displayInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

 
Lombok의 @Builder를 사용하면 아래와 같이 객체를 더 간결하게 생성할 수 있습니다.

import lombok.Builder;

@Builder
public class User {
    private final String name;
    private final int age;
}

 
Lombok 사용을 위한 설정( Maven or Gradle) : 
ㄱ.  Maven (pom.xml)
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version> <!-- 최신 버전 확인 -->
    <scope>provided</scope>
</dependency>

ㄴ. Gradle (build.gradle)
dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.28'
    annotationProcessor 'org.projectlombok:lombok:1.18.28'
}


결론 : 

빌더 패턴을 사용하면 가독성이 향상되고 유지보수가 쉬워짐
메서드 체이닝 방식(set 메서드)을 활용하여 객체 생성을 직관적으로 처리 가능
객체의 불변성을 유지할 수 있음
빌더 패턴을 활용하면 더 유연하고 유지보수하기 좋은 코드를 작성할 수 있습니다. 
 

 

+ Recent posts