Spring Boot 생성자 Static Factory Method 변환 작업 (With Effective Java)

최민길(Gale)·2023년 6월 11일
1

Spring Boot 적용기

목록 보기
22/46

안녕하세요 오늘은 기존에 작성한 생성자들을 Static Factory Method로 변환하는 작업을 진행해보겠습니다.

우선 생성자와 정적 팩토리 메서드에 대해 알아보겠습니다. 생성자(Constructor)는 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드입니다. 흔히 알고 있는 new Class()와 같은 형식으로 인스턴스를 생성하는 메서드입니다.

그렇다면 정적 팩토리 메서드란 무엇일까요? 단어 하나하나를 먼저 해석해보겠습니다. 우선 정적(static)이란 단어 그대로 "고정된"이라는 뜻을 가지고 있습니다. 이를 이해하기 위해선 프로세스의 메모리 할당에 대해서 알아야 하는데, 메모리에는 크게 스택, 힙, 코드, 데이터 이렇게 4가지 공간이 존재합니다. 이 중에서 스택의 경우 지역 변수와 매개 변수, 힙 영역에서는 생성된 객체 등과 같은 동적 메모리가 할당되며, 코드에는 함수, 상수 등이 저장되고 데이터 영역에는 전역 변수 및 정적 변수가 저장됩니다. 즉 위에서 static 처리를 한 정적 변수 또는 정적 메소드는 이 데이터 영역에 저장됩니다.

이 때 사용되지 않는 static 변수 또는 메소드들은 불필요한 메모리를 점유하게 되어 시스템 성능을 하락시키는 원인이 되기도 합니다. 이를 이해하기 위해선 가비지 컬렉터(GC)에 대해서 알아야 하는데, 가비지 컬렉터란 시스템에서 더 이상 사용하지 않는 동적 할당된 메모리 블럭을 사용 가능한 자원으로 회수하는 가비지 컬렉션을 수행하는 역할을 담당합니다. 이 때 포인트는 "동적" 할당된 메모리 블럭만 진행한다는 것입니다. 즉 위의 메모리 영역 중 힙 영역에서 사용하지 않는 데이터를 회수하는 과정을 통해 성능 향상을 이끌어냅니다. 하지만 static 변수 또는 메소드들은 힙 영역이 아닌 데이터 영역에 저장되어 가비지 컬렉션 작업이 일어나지 않아 사용하지 않는 static 데이터가 너무 많으면 불필요하게 메모리를 점유하게 되어 성능 하락을 유발할 수 있습니다.

그럼 이어서 팩토리 메서드에 대해서 알아보겠습니다. 팩토리 메서드란 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만, 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 패턴입니다. 이해하기 쉽게 정리해서 말씀드리자면 자식 클래스에서 어떤 타입의 객체를 생성할 것인지 결정하는 방식으로, 부모의 인터페이스를 통해 자식 클래스에서 이를 implements하여 생성할 객체를 오버라이딩하는 방식입니다. 이를 통해 부모와 자식간의 결합도가 낮아져 확장하는데 용이하다는 장점이 있습니다.

그럼 정적 팩토리 메서드를 다시 살펴보자면, 팩토리 메서드 패턴을 적용한 정적 메서드라고 볼 수 있습니다. 즉 자식 클래스에서 부모 클래스의 객체를 생성하기 위해 static 메서드로 접근하여 생성자를 사용하지 않고 부모 클래스에서 정의한 클래스 인스턴스를 생성할 수 있습니다.

조슈아 블로크(Joshua Bloch)의 이펙티브 자바에서 생성자 대신 정적 팩토리 메서드로 진행하는 것이 더 좋은 5가지 이유를 다음과 같이 설명합니다.

  1. 생성자에 비해 이름을 가질 수 있다.
    --> new Class() 방식으로 인스턴스를 생성하는 것이 아닌 Parents.getInstance()와 같은 방식으로 인스턴스를 생성하기 때문에 코드 가독성이 좋아진다.

  2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
    --> 부모 클래스에서 싱글톤 패턴으로 이미 생성한 객체가 있다면 이를 재활용하여 제공할 수 있다.

  3. 반환 타입의 하위 타입의 객체를 반환할 수 있다.
    --> 팩토리 메서드 특성상 자식 클래스에서 어떤 타입의 객체를 생성할 것인지 결정하게 되므로 부모 클래스에서 하위 타입을 리턴하는 메서드를 통해 하위 타입의 객체를 반환할 수 있다.

  4. 입력 매개변수에 따라 매번 다른 객체를 반환할 수 있다.
    --> 매개변수에 따라 다양한 메서드를 생성할 수 있기 때문에 인스턴스 생성의 유연성이 향상된다,

  5. 정적 팩토리 메서드를 작성하는 시점에서 반환할 객체의 클래스가 존재하지 않아도 된다.
    --> 팩토리 메서드 특성상 자식 클래스에서 어떤 타입의 객체를 생성할 것인지 결정하게 되므로 부모 클래스에서 리턴 클래스의 존재 여부는 중요하지 않다.

직접 예시를 들어보도록 하겠습니다. 다음은 JWT 검증 및 생성을 위한 TokenProvider 클래스의 코드입니다.

    public TokenProvider(
                @Value("${jwt.secret}") String secret,
                @Value("${jwt.token-validity-in-seconds}") long tokenValidityInMilliseconds
	){
		this.secret = secret;
		this.tokenValidityInMilliseconds = tokenValidityInMilliseconds;
    }

위 방식대로 코드를 작성할 경우 외부에서 이 클래스를 가져오기 위해선 new TokenProvider()를 이용하여 인스턴스를 생성하게 됩니다.

다음은 정적 팩토리 메서드를 적용한 코드입니다.

    private static TokenProvider tokenProvider = null;
    public static TokenProvider getInstance(){
        if(tokenProvider == null){
            tokenProvider = new TokenProvider();
        }
        return tokenProvider;
    }

보이시는 것처럼 static 메서드 내부에 객체를 생성하고, 이미 객체가 할당되었다면 할당된 객체를 리턴하는 것으로(싱글톤 패턴) 객체 생성을 관리할 수 있습니다. 이 때 클라이언트에서는 TokenProvider.getInstance() 와 같이 호출하여 생성자를 이용하지 않고 인스턴스에 접근할 수 있습니다.

하지만 이 방식 역시 단점이 존재합니다. 책에서 얘기하는 대표적인 단점으로 별도 public한 생성자가 존재하지 않는 이상 하위 클래스를 만들 수 없다는 단점이 있습니다. 위에서 알아본 것처럼 생성자 없이 인스턴스에 접근할 수 있지만 반대로 말하면 상속받을 하위 클래스에서도 생성자에 접근할 수 없어 상속이 불가능합니다. 하지만 오히려 확장이 불가능한 객체를 만드는데 유용할 수 있다는 장점을 가지고 있습니다.

또한 제 짧은 지식으로 추측해보건데 static 특성 상 가비지 컬렉터의 관리를 받지 않아 사용하지 않을 때에도 프로그램 종료 시 까지 메모리에 존재하게 되어 성능 하락 이슈가 발생할 수 있다고 생각됩니다. 이 부분은 책에서 언급이 없어서 추후 테스트를 통해 검증해보도록 하겠습니다. 그럼 이상으로 오늘의 포스팅 마치도록 하겠습니다!

참고 자료
https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
https://yang-droid.tistory.com/52
https://velog.io/@yonii/JAVA-Static%EC%9D%B4%EB%9E%80
https://blog.metafor.kr/163
https://refactoring.guru/ko/design-patterns/factory-method
https://7942yongdae.tistory.com/147

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글