생성자(Constructor)에 대한 고찰

김준석·2023년 2월 1일
0

Controller에 대한 고찰 글

앞서 작성한 Controller에 대한 글에서 처럼, SpringBoot로 개발을 하다보면 @AllArgsConstructor @NoArgsConstructor, @RequredArgsConstructor 등 의 어노테이션을 자주 사용하곤 한다.

역할과, 의미를 대강 알고 사용하는 것과 알고 사용하는 것은 다르기 때문에 좀 더 깊은 이해를 위해 기록을 남긴다

먼저 생성자에 대한 이해를 다시 해보자!


  • 생성자란?
    인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이다.
    주로 멤버변수의 초기화를 위해 사용하거나, 인스턴스를 생성할 때 실행되어야 할 작업을 위해 사용된다.
  • 생성자 예시

    public Referee(int strike, int ball, String nothing) {
        this.strike = strike;
        this.ball = ball;
        this.nothing = nothing;
    }
    
     gamePlay.playGame(new Referee(0,0,"낫싱"));
  • 모든 클래스에는 반드시 생성자가 있어야 한다.
    생성자를 정의하지 않고도 프로그램이 정상적으로 돌아갔던 이유는 컴파일러가 기본 생성자를 생성해줬기 때문이다.
  • 인스턴스를 생성할 때 해당 생성자를 지니고 있는 클래스에 기반하여, Heap 메모리에 객체가 생성된다. 결국 호출하면 객체의 주소가 return된다. 그리고 자바에서 Garbage Collector가 메모리를 수거해간다.

그럼 생성자를 왜 쓸까?


클래스에 선언된 멤버 변수들은 타 클래스에서 마음대로 접근할 수 없게끔 접근 제어자를 private로 선언한다.
그렇기에 다른 객체를 인스턴스로 생성할 때 변수의 값을 주며 생성하기 위해 사용하는 것이다.

이 외에도 다른 객체에서 값을 주기 위한 방법이 몇가지 있다.

Setter

  • 외부에서 객체 데이터에 직접적으로 접근하는 것을 막기 위해 Setter 메서드를 사용한다.
    스프링에서는 @Setter나 @Data 어노테이션으로 편하게 쓰기도 한다.
  • 하지만, 객체의 일관성이 떨어진다. 객체 외부 어디에서나 이 값을 변경시킬 수 있어진다.
  • Setter를 구현함으로써 불필요한 확장 가능성을 열어두게 된다. 이는 SOLID법칙 중 개방 폐쇄의 원칙(Open-Close Principle)을 위배하게 된다.

    public void setBall(String nothing){
        this.nothing = nothing;
    }

하지만!
나는 Setter의 사용을 지양하려고 한다. 아예 안 쓰진 않지만 최대한 안쓰려고 노력중이다.
왜?

  • 나중에 내가 내 코드, 혹은 다른 사람이 내 코드를 봤을때, Setter를 통해 값을 변경시킨 의도를 찾기 어렵기 때문이다.
  • 의도가 분명히 드러난 메서드를 사용해야 유지보수가 쉬워지기 때문이다.

    소프트웨어 공학에서 유지보수 용이성은 프로그램의 생명을 좌우한다. 따라서, 의도가 확실하지 않은 setter의 사용을 지양하려 한다.

setBall 대신에 의도를 분명히 드러낼 수 있는 방안으로 별도의 메서드를 만들면 해결될 수 있다.

   public void setPassword(String password){
        this.password = password;
    }

    public void changePassword(String newPassword){
        this.password = newPassword;
    }

비밀번호를 변경하기 위해 setPassword()를 사용하는거보다 changePassword()를 사용하는 것이 가독성이 더 좋다.

그럼 Setter 말고 뭐쓰는데요 ㅡㅡ


  • 먼저, 앞서 쓴 생성자의 방법이다! 다만.. 생성자에는 매개변수가 필요하고, 특정 값만 수정하고 싶을 때 새로운 생성자를 만들어줘야 한다.
  • 또한 매개변수가 많아지면.. 각각 매개변수가 무엇을 의미하는지 파악하기 어렵다.

    public User(Long id, String userName, String userNickname, String userEmail, String password){
        this.id = id;
        this.userName = userName;
        this.userNickname = userNickname;
        this.userEmail = userEmail;
        this.password = password;
    }

  • 물론 대단한 인텔리제이에서 각각 매개변수가 무슨 변수를 의미하는 지 알려주긴 하지만.. 그래도 매개변수가 많아지면 불편해질 것이다.

그렇기 때문에! Builder패턴을 사용한다.


Builder 패턴이란?

  • 여러 디자인 패턴중 생성 패턴(Creational Pattern)에 속하는 패턴이다.
  • 별도의 Builder 클래스를 만들어 필수 값에 대해서는 생성자를, 선택적인 값에 대해서는 메서드를 생성하여
    step-by-step으로 값을 받고, 최종적으로 하나의 인스턴스를 뱉는 방식이다.

앞서 쓴 생성자와 다르게 빌더를 사용하면

  • 필요한 데이터만 쓸 수 있다.
  • 유연성을 확보할 수 있다.
  • 가독성을 높일 수 있다.
  • 불변성을 확보할 수 있다.

이런 장점이 있다.

필요한 데이터만 쓸 수 있다.

  • 앞서 쓴 것처럼 데이터가 선택적으로 필요한 경우, 생성자를 사용한다면 새로운 생성자를 추가해줘야하는 번거로움이 있을 수 있다.
  • 번거로움 뿐만 아니라 Entity를 무겁게 만들어 코드의 가독성이 떨어지는 이유도 크다고 생각한다.

유연성 확보 Issue

        User seokki = new User(1L,"준석","닉네임","naver","213");

이런 상황에서 특정한 (ex)키나 몸무게)와 같은 변수를 추가하려면 기존의 모든 코드를 수정해야 하는 번거로움이 있다.

  • 빌더 패턴을 사용하면 기존의 코드에 영향을 주지 않을 수 있다.

가독성을 높일 수 있다.

        User seokki = new User(1L,"준석","닉네임","naver","213");

1L, 213, naver 등이 무엇을 의미하는지 알기 어렵다.
그럼 결국 이 생성자를 선언한 클래스로 가서 확인을 해야한다.

    
    public Member toEntity() {
        return Member.builder()
                .id(id)
                .email(email)
                .nickname(nickname)
                .password(password)
                .build();
    }

반면 Builder 패턴을 사용하여 직관적으로 데이터를 파악할 수 있다.

불변성 확보

앞에 Setter 처럼 개방폐쇄원칙에 위배되기 때문이다.


음.. 정리하다보니 원래 정리하려던 Constructor에서 멀리까지 와버렸네..

이제 내가 자주 남발?하는 @~~Constructor 들에 대해 정리하겠다.

@AllArgsConstructor란?

  • 클래스 내부의 모든 field마다 인자로 받는 생성자를 만들어주는 Annotation이다.
  • 선언된 field의 순서대로 생성자 파라미터를 생성한다.

@RequiredArgsConstructor란?

  • 특별한 처리가 필요한 각 field마다 하나의 파라미터를 생성해준다.
  • 모든 final field와 @Nonnull로 표시된 field까지 모두 파라미터를 생생성한다.

하지만..

Lombok에서는 AllArgsConstructor와 @RequiredArgsConstructor의 사용을 주의하거나, 권하지 않는다.

왜?

  • field의 순서대로 생성자 파라미터를 생성하는데, 선언된 순서를 임의로 바꾸면 IDE와 Lombok에서 이를 알아채지 못하게 된다. 이는 심각한 비즈니스 로직 에러를 발생시킬 수 있다.

NoArgsConstructor

  • 파라미터가 없는 기본 생성자를 만들어준다.
  • 하지만, 초기화가 필요한 final이 붙은 field에 이 어노테이션을 사용하면 컴파일 에러가 뜬다.

Jpa에서는 프록시 생성을 위해 기본 생성자를 반드시 하나 생성해야 한다. 이때 접근 권한을 protected가 아닌 public으로 할 경우에 문제가 발생할 수 있다.

@NoArgsConstructor(access = AccessLevel.PROTECTED)

@NoArgsConstructor(access = AccessLevel.PUBLIC)

생성된 객체의 인자값이 null이 된다면 큰 문제를 발생시킬 수 있다.

그렇기 때문에 기본 생성자의 접근을 protected로 하길 권장한다고 한다.


기록을 남기면서

아무 생각 없이, 기계적으로 어노테이션을 남발해왔던 것 같다. 그게 습관이 됐겠지. 지난 나의 모습에 반성하고... 앞으로는 코드 한 줄 한줄에 의미를 담아 작성하는 습관을 꼭 들여야 겠다.
아직 졸업 작품 개발에 여유가 있으니, 차근차근 고민하며 좀 더 좋은 코드를 만들 수 있는 습관을 길러야겠다.

profile
기록하면서 성장하기!

0개의 댓글