개발지식 먹는 하마 님의 블로그

[내일배움캠프 24일차] _ Builder Pattern로 코드 품질 향상 + 가변 인자 본문

내일배움캠프 (CS25)

[내일배움캠프 24일차] _ Builder Pattern로 코드 품질 향상 + 가변 인자

devhippo 2025. 3. 21. 21:17

챌린지반 코드 리팩토링 시간을 통해 키오스크 과제를 다른 사람들은 어떻게 해결했는지를 알 수 있었다.
해당 코드들이 어떤 면에서 클린하고 좋은 코드인지를 알 수 있어 굉장히 유익했다.
그중에서도 가장 기억에 남는 부분은 빌더 패턴과 유사한 방법의 MenuItem 생성 코드였다.

List<Menu> menu = Arrays.asList(
    new Menu("햄버거").addMenuItems(
		//햄버거 메뉴들...
    ),
    new Menu("음료").addMenuItems(
        //음료 메뉴들...
    ),
    new Menu("사이드").addMenuItems(
		//음료 메뉴들...
    )
);

 //출처 : withong님 github 코드

 

📌 Builder Pattern

객체 생성 시 생성자의 인자가 많을 때 가독성을 높이고, 객체의 불변성을 유지할 수 있도록 도와주는 디자인 패턴

 

가독성 향상: 메서드 체이닝을 활용하여 명확하고 직관적인 객체 생성
불변성 유지: final 필드를 사용하여 객체를 변경할 수 없도록 설정 가능
유지보수 용이: 생성자 오버로딩을 줄이고, 선택적 매개변수를 쉽게 추가 가능
코드의 안정성: 인자의 순서를 실수로 바꿔도 컴파일 타임에서 에러 감지 가능

 

✏️ 생성자 오버로딩의 한계 

public class User {
    private String name;
    private int age;
    private String email;
    private String address;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    //생성자의 매개변수가 많아질수록 늘어나는 생성자...
}
  • 생성자의 매개변수가 많아질수록, 변수 개수에 따라 오버로딩을 계속 추가해야 하는 문제가 발생한다.
    이름만 받는 생성자
    나이만 받는 생성자
    이메일 주소만 받는 생성자
    이름과 나이만 받는 생성자 등...

  • 인자의 순서를 잘못 입력할 가능성도 높아진다.
    (이름이 먼저였나? 나이가 먼저였나?)

빌더 패턴을 사용하면 불필요한 오버로딩을 제거할 수 있다.

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

    //private 생성자 (Builder만 객체를 생성할 수 있도록 설정)
    private User(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.address = builder.address;
    }

    //Builder 클래스 (User의 내부 정적 클래스)
    public static class Builder {
        private String name;
        private int age;
        private String email;
        private String address;

        public Builder(String name, int age) { // 필수 매개변수
            this.name = name;
            this.age = age;
        }

        public Builder email(String email) { // 선택적 매개변수
            this.email = email;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public User build() { // 최종 객체 생성
            return new User(this);
        }
    }
}

 

위와 같이 빌더를 따로 생성하고 이를 이용해 아래와 같이 메서드 체이닝을 활용해 객체를 생성하게 되면,
Stream을 사용할 때와 유사한 형태로 코드가 작성되며
원하는 매개변수만 선택적으로 설정할 수 있다.

이때, 메서드 체이닝을 사용하려면 빌더의 각 메서드가 this를 반환해야 한다.

public class Main {
    public static void main(String[] args) {
        // 빌더 패턴을 사용하여 User 객체 생성
        User user = new User.Builder("John Doe", 25)
                        .email("john@example.com")
                        .address("New York")
                        .build();
    }
}

 

📌 4. 빌더 패턴 vs 생성자 vs Setter

방식 빌더 패턴 Setter 생성자
가독성 매우 높음⭐⭐⭐⭐⭐ 높음⭐⭐⭐ 낮음⭐
불변성 유지 가능✔️ 불가능❌ 가능✔️
확장성 매우 높음⭐⭐⭐⭐⭐ 높음⭐⭐⭐ 낮음⭐
매개변수 선택 가능✔️ 가능✔️ 불가능❌

생성자, Setter와 성능을 비교했을 때 안 쓸 이유가 없어보이는 성능이다. 


 

빌더 패턴과 유사하게 Menu를 생성할 때 MenuItem을 가변인자를 사용해 추가하는 방식도 인상적이었다.

    public Menu addMenuItems(MenuItem... menu) {
        // 전달된 menu를 리스트로 변환하여 menuItems 리스트에 추가
        this.menuItems.addAll(Arrays.asList(menu));
        // 현재 Menu 객체 반환
        return this;
    }
    
    //출처 : withong님 github 코드

 

📌 가변 인자 Varargs

메서드의 매개변수 개수를 가변적으로 받을 수 있도록 지원하는 기능이다.
배열을 사용하지 않고도 여러 개의 인자를 넘길 수 있다.

✅ 내부 동작

가변 인자는 내부적으로 배열로 변환된다.
따라서, 실제로는 매개변수 타입 배열을 선언한 것과 동일한 의미이다.

public void printNumbers(int... numbers) { }  // 가변 인자

public void printNumbers(int[] numbers) { }   // 배열로 직접 선언

 

✅ 가변인자 사용법

매우 간단하다!
메서드의 매개변수 타입 뒤에 세 개의 점 ...을 붙여서 사용한다.

public void 메서드명(매개변수 타입... 변수명)


가변 인자를 사용할 때, 가변 인자만 매개변수로 받을 수 있는 것은 아니다.
일반 매개변수도 함께 사용 가능하다.

public void 메서드명(일반 매개변수1, 일반 매개변수2, 매개변수 타입... 변수명)
  • 단, 맨 마지막에 위치해야 한다.
  • 가변인자는 한 메서드에 한 번만 사용할 수 있다.
  • 오버로딩 시, 오류가 발생할 가능성이 있다.
public void printNumbers(int... numbers) { }
public void printNumbers(int num) { } // 가변 인자와 혼동될 수 있어 오류 발생 가능

 

 

✅ 오버로딩 발생 시, 해결 방법

  • 다른 타입의 매개변수를 추가해 명확하게 구분한다.
  • 가변 인자 대신 명시적으로 배열을 선언한다.
  • 메서드 이름을 다르게 설정한다.