용로그
article thumbnail

정적 팩토리 메서드란?


자바 프로그래머라면 한 번쯤 들어봤을 정적 팩토리 메서드에 대해서 알아보자. 정적 팩토리 메서드가 무엇일까? 말 그대로 메서드로 정적 인스턴스를 생성한다는 뜻일 것이다.

 

여기서 팩토리라는 용어가 조금 생소할 수 있다. GoF 디자인 패턴 중 팩토리 패턴에서 유래한 이 단어는 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다.

 

생성자 대신 정적 팩토리 메서드를 고려하자


Example 1) Dealer

좀 더 쉽게 말하면 객체 생성의 역할을 하는 것이 생성자가 아닌 메서드가 대신 해주는 것이다. 말로만 설명하면 뭐가 뭔지 이해하기 쉽지 않을 것이다. 아래의 코드를 보자.

 

public class Dealer extends User {

    private static final int CAN_RECEIVE_DEALER_MAX_NUMBER = 16;

    private Dealer(final String name, final Hand hand) {
        super(name, hand);
    }

    @Override
    public boolean canReceiveCard() {
        return CAN_RECEIVE_DEALER_MAX_NUMBER >= calculateTotalValue();
    }

    public static Dealer getInstance() {
        return new Dealer("딜러", Hand.create());
    }

    public String getName() {
        return super.getName();
    }
}

 

public class Dealer extends User {

    private static final int CAN_RECEIVE_DEALER_MAX_NUMBER = 16;

    public Dealer(final String name, final Hand hand) {
        new Dealer(name, hand);
    }

    @Override
    public boolean canReceiveCard() {
        return CAN_RECEIVE_DEALER_MAX_NUMBER >= calculateTotalValue();
    }

    public String getName() {
        return super.getName();
    }
}

위의 Dealer 클래스는 User 추상 클래스를 상속 받고 canReceiveCard 메서드를 구현하고 있다. 근데 무언가 괴리감이 들지 않는가? 생성자의 접근 제어자가 private이다.

 

이 말은 즉슨 외부에서 Dealer 인스턴스를 생성해 사용할 수 없다는 것이다. 그러면 Dealer 인스턴스가 필요하다면 어떻게 사용할 수 있을까?

 

바로 메서드에서 Dealer 객체 자체를 반환하도록 하면 될 것이다. 이에 관련된 메서드는 getInstance 메서드다. getInstance 메서드는 Dealer의 객체를 새로 생성하여 반환해주는 역할을 하고 있다.

 

Example 2) LocalTime

다른 예시를 보자. 아래는 LocalTime 클래스의 정적 팩토리 메서드다.

 

public static LocalTime of(int hour, int minute) {
    HOUR_OF_DAY.checkValidValue(hour);
    if (minute == 0) {
        return HOURS[hour];  // for performance
    }
    MINUTE_OF_HOUR.checkValidValue(minute);
    return new LocalTime(hour, minute, 0, 0);
}

 

위의 of 메서드는 생성자로 객체를 생성하는 것이 아닌, 시간(hour)과 분(minute)를 입력받아 LocalTime 객체를 새로 생성해 반환해주고 있다. 

 

정적 팩토리 메서드의 장점이 뭘까?


이름을 가질 수 있다.

생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 즉, new 객체명(param1, param2...);와 같은 식의 객체 생성으로는 그 객체가 무슨 일을 하는지 알기가 힘들다는 뜻이다.

 

예를 들어 기본 생성자인 BigInteger(int, int, Random)과 정적 팩터리 메서드인 BigInteger.probablePrime 중 값이 소수인 BigInteger를 반환한다는 의미를 잘 설명하고 있는것은 무엇일까?

 

당연히 후자가 객체의 특성에 대해서 더 잘 설명하고 있다. 그렇다면 이런 의문이 들 수도 있다. 객체의 특성을 잘 나타내기 위해서 하나의 객체에 여러개의 정적 팩터리 메서드를 만들어도 될까?

 

결론부터 말하면 여러개의 정적 팩터리 메서드를 만드는 것은 Application에게 유연성을 제공할 수 있다. 예를 들어, 자바의 Calendar 클래스는 getInstance() 메서드를 제공한다.

 

이 메서드는 Calendar 클래스의 인스턴스를 반환하며, 클라이언트는 이 메서드를 호출하여 새로운 Calendar 객체를 생성할 수 있다. 또한 Calendar 클래스는 getInstance(TimeZone zone)getInstance(Locale aLocale) 메서드도 제공한다.

 

이러한 다양한 정적 팩토리 메서드를 통해 클라이언트는 시간대 또는 로케일을 기반으로 다른 형태의 Calendar 객체를 생성할 수 있다.

 

따라서, 여러 개의 정적 팩토리 메서드를 정의하여 객체를 생성하는 것은 가능하다. 이는 유연성을 제공하고, 클라이언트가 더 많은 선택지를 가지고 객체를 생성할 수 있도록 도와준다.

 

호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.

반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩토리 메서드 방식의 클래스는 언제 어느 인스턴스를 살아있게 할 지 철저히 통제할 수 있다. 인스턴스를 통제한다면 아래와 같은 장점이 있다.

  1. 싱글턴 패턴으로 객체를 유지할 수 있다.
  2. 단 하나의 객체만을 보장하기 때문에 객체를 인스턴스화 불가로 만들 수 있다. 
  3. 불변 값 클래스에서 서로 같은 인스턴스가 단 하나뿐임을 보장할 수 있다.
  4. 플라이웨이트 패턴의 근간이 되며, 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다.

플라이웨이트 패턴
객체 지향 소프트웨어 디자인 패턴 중 하나다. 이 패턴은 객체 생성 비용을 최소화하기 위해 공유 객체를 사용하는 방법을 제공한다.

이 패턴은 큰 개수의 비슷한 객체들을 만들어야 할 때 유용하다. 예를 들어 게임에서 많은 수의 비슷한 적을 생성해야 하는 경우, 모든 적 객체에 대해 개별적인 인스턴스를 만드는 것보다는 한 개의 인스턴스를 만들고 이를 공유하여 사용하는 것이 더 효율적이다.

이 패턴은 객체를 두 개의 클래스로 분리한다. 하나는 상태를 나타내는 클래스이고, 다른 하나는 이 상태를 공유하는 클래스다. 이렇게 분리함으로써 상태를 공유하는 클래스는 여러 개의 객체에서 공유될 수 있으므로 객체 생성 비용을 줄일 수 있다.

플라이웨이트 패턴은 메모리 사용량을 줄이고 성능을 향상시키는 데 도움이 되며, 대규모 시스템에서 자주 사용된다.

 

위에서 봤던 Dealer 객체의 인스턴스를 통제하고 싶다면 아래와 같이 변경할 수 있다.

 

public class Dealer extends User {

    private static final int CAN_RECEIVE_DEALER_MAX_NUMBER = 16;
    private static final Dealer DEALER = new Dealer("딜러", Hand.create());

    private Dealer(final String name, final Hand hand) {
        super(name, hand);
    }

    public static Dealer getInstance() {
        return Dealer.DEALER;
    }
    
    @Override
    public boolean canReceiveCard() {
        return CAN_RECEIVE_DEALER_MAX_NUMBER >= calculateTotalValue();
    }

}

 

원래는 getInstance 메서드에서 new 키워드로 새로운 Dealer 객체를 반환했는데, Dealer를 상수로 만들어 둠으로써 getInstance 메서드 객체의 인스턴스화를 막은 모습이다.

 

반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

정적 팩터리 메서드(Static Factory Method)는 객체를 생성하는 메서드다. 이 메서드는 객체 생성을 위한 "new" 연산자 대신 사용된다. 이 메서드는 객체 생성과 동시에 반환된다.

 

정적 팩터리 메서드의 장점 중 하나는 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이다. 즉, 반환 타입으로 인터페이스나 추상 클래스를 사용하고 구체적인 하위 클래스의 인스턴스를 반환할 수 있다.

 

이러한 방식은 다형성을 사용할 수 있도록 하며, 코드의 유연성과 확장성을 높일 수 있다. 예를 들어, List 인터페이스를 구현하는 ArrayListLinkedList 클래스가 있다.

 

이 때, 정적 팩터리 메서드를 사용하여 List 인터페이스를 반환하면, 호출하는 측에서는 이를 ArrayList 또는 LinkedList로 캐스팅하지 않고도, 반환된 객체를 바로 사용할 수 있다. 이렇게 함으로써 코드의 가독성을 높일 수 있다.

 

    List arrayList = new ArrayList<>();
    List linkedList = new LinkedList();

 

또한, 반환 타입의 하위 타입 객체를 반환하는 능력은 객체 생성과 반환 로직을 구현할 수 있는 유연성을 제공한다. 이는 유지 보수 및 기능 추가 등을 할 때 유용하다.

 

예를 들어, 정적 팩터리 메서드를 사용하여 인자에 따라 다른 하위 클래스의 인스턴스를 반환하도록 하면, 클라이언트 코드의 수정 없이도 객체 생성 로직을 변경할 수 있다.

 

입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있는 장점은 다음과 같다.

  1. 유연한 객체 생성 방법: 생성자는 인스턴스화된 객체의 유형이 고정되어 있다. 하지만 정적 팩토리 메서드는 입력 매개변수에 따라 다른 유형의 객체를 생성할 수 있으므로, 더 유연한 객체 생성 방법을 제공한다.
  2. 코드 가독성 개선: 생성자에 필요한 매개변수가 많아질수록 코드의 가독성은 떨어질 수 있다. 하지만 정적 팩토리 메서드는 입력 매개변수에 의미를 부여할 수 있으므로 가독성을 개선할 수 있다.
  3. 객체 생성을 캡슐화: 정적 팩토리 메서드를 사용하면 객체 생성을 캡슐화할 수 있다. 이를 통해 클라이언트 코드에서는 어떤 클래스의 인스턴스를 생성하는지 알 필요 없이, 단순히 정적 팩토리 메서드를 호출하여 인스턴스를 생성할 수 있다. 이는 클래스의 구현을 변경하더라도 클라이언트 코드에 영향을 미치지 않도록 하는 데 도움을 준다.

이러한 장점들을 통해 정적 팩토리 메서드는 객체 생성 방법의 다양성과 유연성을 제공하면서도 코드의 가독성과 유지보수성을 향상시킬 수 있다.

 


싱글톤 패턴에 관련해서도 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다는 장점 중의 하나인데 위에서 알아봤기 때문에 따로 여기서 언급하지 않았다.

 

그럼 단점은 없어?


상속을 하려면 public이나 protected 생성자가 필요해 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.

앞서 이야기한 컬렉션 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다. 어떻게 보면 이 제약은 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점이 될 수도 있다.

 

정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

생성자처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다. 이 일을 언젠가 자바독이 알아서 처리해줬으면 좋겠지만, 그날이 오기 전까지는 API 문서를 잘 써놓고 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해야 한다.

 

정리


정적 팩토리 메서드public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩토리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.

profile

용로그

@용로그

벨덩보단 용덩 github.com/wonyongChoi05