프로젝트/싹쓰리

[프로젝트] 객체 생성 방법(생성자, getter setter, 빌더)

라임온조 2023. 4. 16. 18:11

1. 서론

어떤 클래스를 만들고 해당 클래스를 사용해서 속성 값을 가지고 있는 인스턴스를 만드려고 할 때 흔히 사용하는 방식에는 생성자를 사용하는 방식, setter로 속성 값을 설정해서 생성하는 방식, builder를 사용하는 방식이 있다. 예전에 get과 set 메서드만 알고 있을 때는 set으로 값을 설정하는 방법을 사용했는데 @builder 어노테이션이 있다는 것을 알게 되고 난 후, 각 방식에 대해 궁금해졌다. 각 방식의 특징은 무엇이고 어떤 방법이 이번 내 프로젝트에 적당할까?

 

2. 각 방식에 대해 알아보기

1) 생성자 사용

① 사용 방법


public class Car {
    private int price;
    private String owner;
    public Car(int price, String owner){
        this.price = price;
        this.owner = owner;
    }
}

// 생성
Car car1 = new Car(1000, "k");

② 특징

  • 각 속성 값이 어떤 것을 의미하는 것인지 알기가 힘들다.
    • 위에서 볼 수 있듯이 car1을 생성할 때 7000원이 뭘 의미하는지, "lee"가 뭘 의미하는지 생성하는 코드만 보면 알기가 힘들다. 물론 인텔리제이를 사용하면 생성자로 인스턴스를 만들 때 각 값 옆에 속성 값의 이름이 깨알같이 적혀 있긴 하다. 하지만 인텔리제이와 같은 도움이 없는 '일반적'인 경우를 생각해 보면 알기 힘들다고 봐야 할 것이다.
  • 속성 값의 순서를 생성자에 있는 것과 다르게 넣을 경우 생성이 되지 않는다.
    • 위의 경우 값이 먼저 오고 그 다음에 주인 정보가 온다. 그래서 생성자로 생성할 때도 해당 순서를 지켜야 한다. 만약 순서를 지키지 않으면 오류가 발생한다. 근데 우리가 생성자 순서를 매번 기억할 수도 없고.. 엄청 귀찮아진다.
  • 생성자에 있는 모든 속성 값을 넣어줘야만 생성이 된다.
    • 속성 값 중 하나라도 넣지 않으면 오류가 난다. 그래서 내가 원하는 값만 넣고 싶어도 그렇게 하려면 그거에 맞는 생성자를 일일이 만들어줘야 한다. 코드가 굉장히 늘어나게 된다.

2) setter 사용

① 사용 방법

@Getter
@Setter
public class Car {
    private int price;
    private String owner;

}

// 생성
Car car1 = new Car();
car1.setOwner("le");
car1.setPrice(100);

② 특징

  • 각 속성값이 어떤 값을 의미하는지 알기 쉽다.
    • setOwner 라고 적혀 있으니 "le"가 owner를 의미하는지를 단번에 확인할 수가 있다.
  • 순서 상관 없이 속성 값을 설정할 수 있다.
    • setOwner를 먼저 하면 그게 먼저 실행되는 거고, setPrice를 먼저 하면 그게 먼저 실행되는 거다. 그래서 순서 상관 없이 값을 설정할 수가 있다.
  • 모든 속성 중 몇 개의 속성만 set으로 설정해 줘도 인스턴스 생성이 가능하다.
  • set 메서드가 있기 때문에 객체가 변화가능하다.
    • Car car1 = new Car(); car1.setPrice(1000); 라고 한 이후 Member member1 = new Member(1, car1); 이라고 했다고 해보자. 그리고 또 다른 멤버를 만들건데, 이 멤버의 car 가격을 바꾸고 싶어서 Car car2 = car1; car2.setPrice(2000); Member member2 = new Member(2, car2); 라고 해버리면... member1의 가격도 2000으로 바뀌어 버린다.
    • 이는 객체를 생성하는 변수가 참조변수로 주소를 가리키고 있기 때문에 발생한다. car2 = car1; 이라고 하면서 car2는 car1이 가리키고 있던 객체를 가리키게 된다. 즉, car2와 car1이 같은 객체를 가리키고 있는 것. 그런데 그 상태에서 car2.setPrice(2222); 이런 식으로 호출을 해버리면 car2와 car1이 바라보고 있는 객체의 price 값이 바뀌어 버린다. 즉 car1은 생뚱맞게 영향을 받아 버린 것이다.

3) builder 사용

ⓛ 개념

한 클래스 안에서 객체를 표현하기 위한 코드와 객체를 생성하기 위한 코드를 따로 분리해서 작성하여 객체를 생성할 수 있도록 하는 방법

 

② 방법

정석

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

    private Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

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

        public Builder() {}

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

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

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

        public Person build() {
            return new Person(name, age, email);
        }
    }
}

name, age, email 메서드를 통해 객체를 표현하고 있다. 그리고 build 메서드를 통해 객체를 생성한다.

 

lombok 어노테이션 활용

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

}


Person person = Person.builder()
                    .name("Jane Doe")
                    .age(35)
                    .email("janedoe@example.com")
                    .build();

어노테이션을 활용하면 위에서 작성해야 했던 긴 코드들을 작성하지 않아도 객체 생성이 가능하다.

 

③ 특징

  • 불변성: setter를 사용할 때와는 달리 객체의 불변성이 보장된다.
    • setter 메서드가 없기 때문에 생성된 객체의 값을 바꿀 수가 없다.
  • 유연성: 순서 상관 없이 생성이 가능하다.
  • 유연성: 필요한 속성만으로 객체 생성이 가능해서 여러 생성자를 오버로딩 하지 않아도 된다.
  • 가독성: 각 속성 값이 의미하는 바를 쉽게 파악할 수 있다.

 

3. 결정

나는 Builder를 선택하기로 했다. 그 이유는 다음과 같다.

일단 가독성이 중요했다. 프로젝트를 진행하는 와중이나, 진행하고 난 이후나 코드를 다시 볼 때 개발하고자 하는 바를 명확하게 이해할 수 있어야 유지보수가 쉽다. 유지보수 뿐만 아니라 내가 작성했던 코드를 다시 보면서 설명하고자 할 때도. 그런데 생성자 방식은 어떤 속성 값인지 알기가 쉽지 않다. 인텔리제이 같은 환경이면 몰라도 깃헙에서 코드만 보면 알기가 힘들 것이다. 

그리고 코드 작성의 편의성도 중요했다. 프로젝트를 진행하면서 모든 엔티티의 속성값이 필요하지는 않을 확률이 높다. 그럴 때마다 생성자를 계속 오버로딩하는 것은 너무 비효율적이다. 또한, 순서 상관없이 속성 값을 넣는 것이 개발 과정에서 훨씬 편하다.

또한, 혹시 모를 객체 변화의 가능성을 막아 비상 사태를 막고자 하였다. 만약 setter를 사용해서 개발하다 setter를 이상한 곳에 사용한다면 개발 과정에서 이상한 오류를 발견하게 될 수도 있다. 이미 그걸 방지할 수 있는 방안이 있는데 굳이 그걸 방지하지 못하는 방식을 사용할 필요는 없다고 생각한다.