728x90

 

객체를 객체를 생성하는 방법은 여러 가지가 있습니다. 각 방식은 특정 상황에서 유리하게 작용하며, 코드의 가독성, 유지보수성, 불변성 등에 영향을 미칩니다.

 

이번 포스팅에서는 자바에서 객체를 생성하는 주요 방식인 생성자, @Setter, 그리고 @Builder 패턴에 대해 알아보고, @Builder 어노테이션을 사용해야 하는 이유에 대해서 알아보고자 합니다.

 

 

생성자(Constructor) 

public class Person {
    private String name;
    private int age;
    private String email;

    // Constructor
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // Getters
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getEmail() { return email; }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Person person = new Person("John Doe", 30, "john.doe@example.com");
        System.out.println(person.getName() + ", " + person.getAge() + ", " + person.getEmail());
    }
}


장점

필수 필드 보장

  •  생성자를 통해 모든 필수 필드한 번에 설정할 수 있습니다. 이는 객체가 항상 유효한 상태로 생성됨을 보장합니다.

불변성

  •  생성자에서 모든 필드를 초기화하면, 객체가 불변(immutable)일 수 있습니다 (특히 final 필드를 사용할 경우).

단점

매개변수 순서

  •  생성자의 매개변수 순서가 중요합니다. 매개변수의 순서가 변경되면 새로운 생성자를 작성해야 합니다.
Person person = new Person("John Doe", "john.doe@example.com", 30);

 

 Person() 생성자는 순서가 name, age, email로 받아야 합니다. 그러나 위 코드와 같이 name, email, age로 생성하게 된다면 컴파일 오류가 발생하진 않지만 객체가 잘못된 상태로 생성됩니다.

다양한 조합

  • 필드의 조합이 많을 경우, 다양한 생성자를 작성해야 하므로 코드가 복잡해질 수 있습니다.
// Constructor with all fields
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // Constructor with name and age, email has a default value
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        this.email = "not_provided@example.com"; // Default value
    }

    // Constructor with only name, age and email have default values
    public Person(String name) {
        this.name = name;
        this.age = 0; // Default value
        this.email = "not_provided@example.com"; // Default value
    }

 

필드의 조합에  따른 다양한 생성자 때문에 코드가 길어졌습니다. 위 경우 3개의 필드만 존재하지만 필드의 수가 많아지게 되면 코드가 아주 복잡해지게 됩니다.

 

@Setter 어노테이션

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Person {
    private String name;
    private int age;
    private String email;
}

// Usage
public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("John Doe");
        person.setAge(30);
        person.setEmail("john.doe@example.com");
        System.out.println(person.getName() + ", " + person.getAge() + ", " + person.getEmail());
    }
}

 

장점

간편한 데이터 설정

  •  객체 생성 후 setter 메서드를 통해 필드를 설정할 수 있습니다. 객체 생성 시 인자를 전달할 필요가 없습니다.

순서 무시

  •  필드를 설정할 때 순서가 중요하지 않습니다.

단점

불변성 부족

  •  @Setter를 사용하면 객체의 상태를 외부에서 변경할 수 있으므로, 불변성을 보장할 수 없습니다.
public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("John Doe");
        person.setAge(30);
        person.setEmail("john.doe@example.com");
        
        // 필드 값을 변경할 수 있음
        person.setName("Jane Doe");
        person.setAge(25);
        person.setEmail("jane.doe@example.com");
        
        System.out.println(person.getName() + ", " + person.getAge() + ", " + person.getEmail());
    }
}

 

위의 코드에서 객체 person의 상태변경하면 출력 결과가 바뀌게 됩니다. 이는 객체가 불변하지 않다는 것을 의미합니다.

 

불변성이 보장되지 않으면 객체의 상태가 예기치 않게 변경될 수 있습니다. 예를 들어, 여러 곳에서 같은 객체를 사용하고 객체의 상태변경하면, 객체를 사용하는 다른 부분에서도 변경된 상태를 보게 됩니다. 이로 인해 데이터의 일관성 문제를 초래할 수 있습니다.

 

 

불필요한 API 노출

  •  모든 필드에 대해 setter가 생성되므로, 객체의 내부 상태가 외부에 노출됩니다.

 

Builder 패턴 (@Builder)

Builder 패턴은 객체 생성 과정에서 복잡한 객체단계별로 구성하고 설정할 수 있는 패턴입니다. 이 패턴은 객체의 생성 과정과 그 객체의 표현을 분리하여 복잡한 객체더 간단하게 생성할 수 있도록 돕습니다. 주로 다음과 같은 상황에서 유용합니다.

 

  • 객체 생성 시 많은 매개변수가 필요한 경우
  • 매개변수의 순서가 중요할 때
  • 선택적인 매개변수가 있는 경우
  • 객체의 불변성을 보장하고 싶은 경우

 

Builder 패턴

public class Person {
    private final String name;
    private final int age;
    private final String email;

    // Private constructor to prevent direct instantiation
    private Person(PersonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
    }

    // Getters
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getEmail() { return email; }

    // Builder class
    public static class PersonBuilder {
        private String name;
        private int age;
        private String email;

        public PersonBuilder name(String name) {
            this.name = name;
            return this;
        }

        public PersonBuilder age(int age) {
            this.age = age;
            return this;
        }

        public PersonBuilder email(String email) {
            this.email = email;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Person person = new Person.PersonBuilder()
                        .name("John Doe")
                        .age(30)
                        .email("john.doe@example.com")
                        .build();
        System.out.println(person.getName() + ", " + person.getAge() + ", " + person.getEmail());
    }
}

@Builder 어노테이션 사용

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class Person {
    private String name;
    private int age;
    private String email;
}

// Usage
public class Main {
    public static void main(String[] args) {
        Person person = Person.builder()
                              .name("John Doe")
                              .age(30)
                              .email("john.doe@example.com")
                              .build();
        System.out.println(person);
    }
}

장점

가독성

Person person = Person.builder()
                      .name("John Doe")
                      .age(30)
                      .email("john.doe@example.com")
                      .build();
  • @Builder를 사용하면 객체 생성 시 필드를 명시적으로 설정할 수 있어 코드의 가독성이 좋습니다.
  • 필드를 설정하는 순서가 명확하며, 생성자 호출 시 매개변수의 순서에 신경 쓸 필요가 없습니다.

불변성 보장

  • 생성자에서 final로 선언된 필드만 사용하면, 객체의 불변성을 보장할 수 있습니다.

순서 무시

  •  빌더 패턴필드 설정 순서가 중요하지 않으며, 필요한 필드만 설정할 수 있습니다.

유연성

@Builder
public class Person {
    private final String name;
    private int age = 0; // 기본값
    private String email = "not_provided@example.com"; // 기본값
}
  •  필드의 기본값을 설정하거나, 필요한 필드만 설정할 수 있어 유연합니다.

체이닝 지원

  •  빌더 메서드가 체이닝 방식으로 호출되므로, 코드가 깔끔하고 자연스럽습니다.

단점

추가적인 클래스 생성

  •  @Builder는 내부적으로 빌더 클래스를 생성하므로, 코드베이스에 추가적인 클래스가 생깁니다. 그러나 이 점은 대부분의 경우 장점으로 작용합니다.

 


각 객체 생성 방식은 특정 상황에서 장단점이 있습니다.

 

@Builder 패턴은 특히 복잡한 객체를 생성할 때 유용하며, 코드의 가독성유지보수성을 높이는 데 큰 도움이 됩니다.

 

@Setter는 간단한 객체의 경우 편리하지만, 불변성을 보장하지 않으며 객체의 상태를 쉽게 변경할 수 있는 단점이 있습니다.

 

생성자는 필수 필드를 보장하고 불변성을 유지할 수 있지만, 다양한 필드 조합에 대해 생성자를 추가로 작성해야 하는 번거로움이 있습니다.

 

코드와 객체 설계의 요구 사항에 맞게 적절한 객체 생성 방식을 선택하는 것이 중요합니다.