정리정리

정적 팩토리 메서드 장점 본문

Java

정적 팩토리 메서드 장점

wlsh 2023. 5. 7. 23:57

정적 팩터리 메서드란?

정적 팩터리 메서드란 생성자를 static 메서드로 감싸 해당 클래스의 인스턴스를 반환하는 메서드입니다.

자바의 boxed class나 collections에서도 많이 볼 수 있죠.

List.of

그렇다면 정적 팩터리 메서드에 어떤 장점이 있는지 이펙티브 자바 책을 기반으로 알아보겠습니다.

정적 팩터리 메서드 장점

1. 생성자가 이름을 가질 수 있음

클린코드 책의 초반부부터 강조하는 내용이 바로 '이름'입니다. 잘 지어진 이름을 통해 클라이언트는 원하는 동작을 기대하고 함수나 메서드를 호출을 합니다.

하지만 생성자는 그 자체만으로 어떤 특징을 가진 인스턴스를 생성하는지 알기 힘듭니다. 특히나 생성자의 파라미터가 늘어나고, 생성자의 종류가 많아질수록 언제 어떤 생성자를 사용해야 할지 알기 힘들어지겠죠.

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    ...

    //OAuth 유저
    public Member(String socialId, String userName, String nickname, String email) {
        ...
    }

    //일반 유저
    public Member(String userName, String nickname, String email, String password) {
        ...
    }
}

이와 같이 Member를 만드는 생성자들이 있다고 가정을 해보겠습니다.

이 Member 클래스를 사용하는 클라이언트 입장에서는 어떤 생성자를 사용해야 OAuth 유저를 저장하는지, 또는 일반 유저를 저장하는지 클래스를 확인해야 알 수 있습니다.

게다가 주석을 달아야 어떤 행동을 하는지 파악할 수 있다는 것 자체가 코드스멜이라고 볼 수 있겠죠.

정적 팩터리 메서드는 이런 부분을 다음처럼 정말 쉽게 해결해 줍니다. 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    ...

    public static createOAuthMember(String socialId, String username, String email) {
        return new Member(...);
    }

    public static createBasicMember(String userName, String email, String password) {
        return new Member(...);
    }
}

이렇게 코드를 작성을 하면 클라이언트는 굳이 Member 클래스를 확인하지 않아도 어떤 메서드가 어떤 멤버 엔티티를 만드는지 알 수 있게 됩니다.

2. 호출할 때마다 인스턴스를 새로 생성하지 않아도 됨

정적 팩터리 메서드는 생성자를 캡슐화 함으로써 항상 인스턴스를 새로 생성하지 않아도 되는 것을 가능하게 합니다.

덕분에 불변 객체를 만들고 싱글턴으로 사용을 하거나 캐싱과 같은 동작을 할 수 있어, 쓸데없는 리소스 낭비를 하지 않아도 됩니다.

@jdk.internal.ValueBased
public final class Boolean implements java.io.Serializable, Comparable<Boolean>, Constable {
    
    public static final Boolean TRUE = new Boolean(true);

    public static final Boolean FALSE = new Boolean(false);

    @IntrinsicCandidate
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    ...
}

대표적인 예로 Boxed Class의 Boolean이 있습니다.

Boolean의 valueOf를 호출하게 되면 새로 Boolean 인스턴스를 반환하는 것이 아닌, 미리 불변 객체로 선언된 두 개의 싱글턴 인스턴스를 리턴하게 됩니다.

이런 식으로 스스로 본인 클래스의 인스턴스를 제어하는 클래스를 인스턴스 통제(instance-controlled) 클래스라고 한다고 합니다.

3. 반환 타입의 하위 타입 객체를 반환할 수 있음

정적 팩터리 메서드는 생성자와 달리 반환 타입을 지정할 수 있고, 이 말은 곧 다형성을 적용시킬 수 있다는 뜻이 됩니다.

public class Dog {}

public class Maltese extends Dog {}

public class Poodle extends Dog {}

public class Dogs {

    public static Dog maltese() {
    	return new Maltese();
    }
    
    public static Dog poodle() {
    	return new Poodle();
    }
}

반환 타입을 인터페이스 또는 추상 클래스로 두고 필요에 따라 알맞은 구현 객체를 리턴함으로써, 클라이언트는 구현 클래스에 대해 자세히 알지 않아도 올바른 동작을 할 거라는 기대를 가지고 개발을 할 수 있습니다.

덕분에 객체 지향의 핵심 중 하나인 추상화를 지킬 수 있게 됩니다.

4. 매개 변수에 따라 반환할 하위 객체를 정할 수 있음

사실 3번과 비슷한 맥락이라고 볼 수 있습니다. 

반환할 하위 객체를 반환할 수 있기 때문에 매개변수를 통해 반환할 하위 객체 또한 정할 수 있겠죠.

다만 개인적으로 이건 장점이라고 보기 어렵다는 생각이 들었습니다.

이펙티브 자바 책에서 예시로 든 EnumSet을 보면서 그 이유를 얘기해보겠습니다.

책에서 예시로 든 EnumSet을 보면, Enum의 원소 개수에 따라 RegularEnumSet 또는 JumnoEnumSet을 리턴합니다.

EnumSet의 noneOf 메서드

이 구현을 보면 EnumSet이 본인의 자식 클래스인 RegularEnumSet과 JumboEnumSet을 참조하고 있습니다.

오히려 추상화가 깨지고, 부모가 자식을 참조하는 역참조가 발생하면서 자식의 변화에 부모가 변할 수 있는 가능성이 생긴 이상한 상황이라는 생각이 드네요.

이 예시보다는 차라리 팩터리 패턴 예시가 조금 더 낫지 않았을까 하는 생각이 들었습니다.

참고

Effective Java 3/E

 

'Java' 카테고리의 다른 글

[Java] assert 알아보기  (0) 2024.05.20
Comments