Java - 정적 팩토리 메서드의 정의와 네이밍 컨벤션

정적 팩토리 메서드(static factory method)는 실무에서도 활용하기 쉬운 프로그래밍 기법입니다. 정확히는 GoF 디자인 패턴 중 팩토리 패턴에서 용어를 가져와 정의한 기법으로 "객체 생성 메서드"라고 정의할 수 있습니다. 프로그래밍을 처음 접한 사람은 조금은 어려울 수 있지만 간단하게라도 개념을 익히고 알아둔다면 프로그래밍을 하는데에 있어 도움이 되는 개념 중에 한 가지입니다.

정적 팩토리 메서드의 정의

정적 팩토리 메서드라는 용어가 생소하게 느껴질 수 있지만, 사실 그리 생소하지 않은 방법입니다. 자바에서 말하는 클래스의 인스턴스화, 즉 객체 생성을 흔히 사용하는 생성자가 아닌 정적(static) 메서드로 하는 것을 정적 팩토리 메서드라고 합니다. 글로만 설명하면 어려울 수 있으니 코드를 통해 정적 팩토리 메서드를 확인해보겠습니다.

정적 팩토리 메소드를 사용한 자바의 Optional

Optional의 of()

 정적 팩토리 메서드의 대표적인 예시 하나가 바로 자바 1.8에서 추가된 Optional입니다. 정적 팩토리 메서드라는 단어 때문에 어렵다고 생각하지 마세요. 우리는 정확한 단어만 모르고 있었을 뿐 알게 모르게 정적 팩토리 메서드를 자주 사용하고 있었으니까요. 

 

 자바 1.8부터 제공된 Optional은 정적 팩토리 메서드를 정석으로 보여주는 객체입니다. 앞서 정적 팩토리 메서드는 객체를 생성하는 메서드라고 정의했었는데 Optional은 "new" 키워드 대신 "of()" 메서드를 이용해 객체를 만들 수 있게 설계되어있습니다. 이제 메서드가 객체를 생성한다라는 개념이 무엇인지 조금은 감히 잡히지 않나요?

 

 그런데 여기서 중요한 점은 왜 자바에서 정의한 객체를 생성하는 방법인 "생성자"를 사용해서 객체를 만들지 않고 메서드를 사용해 객체를 생성하게 만들었는지입니다.

팩토리 메서드 아는 척하기 .feat 네이밍 컨벤션

정적 팩토리 메서드를 사용하는 이유를 알아보기 전에 머리도 식힐 겸 간단하게 정적 팩토리 메서드의 네이밍 컨벤션을 알아보겠습니다. 정적 팩토리 메서드에서 네이밍 컨벤션을 알아두는 것은 개념을 아는 것만큼 중요합니다.

 

- from: 하나의 매개 변수를 받아서 인스턴스를 생성해서 반환합니다.

import java.util.Optional;

public class Application {
    
    public static void main(String[] args) {
        Optional<Long> value = Optional.of(1L);
    }
}

- of: 여러 개의 매개 변수를 받아서 인스턴스를 생성해서 반환합니다.

import java.util.EnumSet;

public class Application {
    public enum TestValue {
        ONE,
        TWO,
        THREE
    }

    public static void main(String[] args) {
        EnumSet.of(TestValue.ONE, TestValue.TWO, TestValue.THREE);
    }
}

- getInstance | instance: 인스턴스를 생성해서 반환합니다. 하지만 항상 동일한 인스턴스를 반환한다는 보장을 하지 않습니다. 주로 싱글톤(singleton)을 구현할 때 많이 사용되는 규칙이지만, 싱글톤과는 무관하며 동일한 인스턴스 반환을 보장하지 않습니다.

import java.util.Calendar;

public class Application {

    public static void main(String[] args) {
        Calendar instance = Calendar.getInstance();
    }
}

- newInstance | create: 항상 새로운 인스턴스를 생성해서 반환합니다. getInstace | instance와 유사한 개념이지만 항상 다른 인스턴스를 반환한다는 개념이 다릅니다.

import javax.xml.parsers.DocumentBuilderFactory;

public class Application {

    public static void main(String[] args) {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
    }
}

- get[OtherType]: 특정한 타입의 인스턴스를 생성해서 반환합니다. getInstance처럼 인스턴스를 반환하지만, 전혀 다른 유형의 인스턴스[OtherType]를 반환한다는 차이점이 있습니다.

 

- new[OtherType]: 특정한 타입의 새로운 인스턴스를 생성해서 반환한다. newInstance처럼 인스턴스를 반환하지만, 전혀 다른 유형의 인스턴스[OtherType]를 반환한다는 차이점이 있습니다.

예제로 알아보는 정적 팩토리 메서드의 장점 .feat 이펙티브 자바

위에서는 분위기를 환기시킬 겸 정적 팩토리 메서드(static factory method) 네이밍 컨벤션을 알아보았습니다. 이번에는 생성자(constructor)를 두고 메서드로 객체를 생성하면 어떤 장점이 있는지 알아보도록 하겠습니다.

이름을 가질 수 있다.

 코드를 만들 때 중요한 요소 중에 하나가 코드를 읽기 쉽도록 작성해야 한다는 점입니다. 그런 의미에서 new라는 키워드를 이용해 객체를 생성하는 방법은 어떠한 편의성도 제공하지 않습니다. 단지 매개변수의 유형과 개수를 제한할 뿐입니다.

public class Application {
    public static void main(String[] args) {
        Car car = new Car("현대");
    }
}

class Car {
    private String brand;

    public Car(String brand) {
        this.brand = brand;
    }
}

 반면 정적 팩토리 메서드를 이용해 의미 있는 이름을 지어 객체를 생성한다면 코드를 쉽게 이해하고 사용할 수 있습니다.

public class Application {
    public static void main(String[] args) {
        Car car = Car.brandOf("현대");
    }
}

class Car {
    private String brand;

    private Car(String brand) {
        this.brand = brand;
    }
    
    public static Car brandOf(String brand) {
        return new Car(brand);
    }
}

 정적 팩토리 메서드 네이밍을 활용해 자동차 객체를 만드는 정적 팩토리 메서드를 만들어서 사용해보았습니다. 네이밍을 알고 있다면 어떤 값을 이용해 자동차 객체를 만들려고 하는지 쉽게 설계 의도를 전달할 수 있습니다.

객체 생성을 관리할 수 있다.

정적 팩토리 메서드는 기본적으로 객체 생성을 책임지고 있기 때문에 객체의 생성을 관리한다고도 할 수 있습니다. 즉 필요에 따라 항상 새로운 객체를 생성해서 반환할 수도 있고, 하나의 객체만 계속 반환할 수도 있습니다. 객체 생성을 관리할 수 있다는 이야기는 불필요한 객체를 만들지 않을 수 있다는 뜻을 내포하고 있습니다.

class Singleton {
    private static Singleton singleton = null;

    private Singleton() {}

    static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }

        return singleton;
    }
}

 싱글톤(singleton)을 예제로 설명한다면, getInstance(정적 팩토리 메서드)를 사용해 하나의 객체만 사용하게 만든 부분이 객체 생성을 관리한다고 이야기해볼 수 있습니다.

사용하는 구현체를 숨길 수 있다.

 정적 팩토리 메서드는 객체 생성만 할 뿐 구현부는 외부에 보여주지 않는 특징이 있습니다. 노출하지 않는다는 특징은 은닉성을 가지기도 하지만 동시에 사용하고 있는 구현체를 숨겨 의존성을 제거해줍니다. 이 부분도 예제 코드를 통해 확인해볼까요?

public class Application {
    public static void main(String[] args) {
        System.out.println(GradeCalculator.of(88).toText());
        System.out.println(GradeCalculator.of(50).toText());
    }
}

class GradeCalculator {
    static Grade of(int score) {
        if (score >= 90) {
            return new A();
        }

        if (score >= 80) {
            return new B();
        }

        return new F();
    }
}

interface Grade {
    String toText();
}

class A implements Grade {
    @Override
    public String toText() {
        return "A";
    }
}

class B implements Grade {
    @Override
    public String toText() {
        return "B";
    }
}

class F implements Grade {
    @Override
    public String toText() {
        return "F";
    }
}

 Grade를 사용하는 main()에서는 Grade 인터페이스를 사용함으로써 구현체인 A, B, F를 몰라도 전혀 문제가 없습니다. 구현체를 알고 생성해서 반환할 책임은 오롯이 정적 팩토리 메서드를 가진 GradeCalculator입니다.

정적 팩토리 메서드에 단점은 없는 걸까?

자 마지막으로 정적 팩토리 메서드가 가진 단점을 알아볼 차례입니다. 정적 팩토리 메서드는 장점만 있고 단점은 없는 걸까요? 아닙니다. 정적 팩토리 메서드에는 단점도 존재합니다. 그럼 어떤 문제가 있는지 확인해보도록 하겠습니다.

class Singleton {
    private static Singleton singleton = null;

    private Singleton() {}

    static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }

        return singleton;
    }
}

 이 예제는 앞서 살펴보았던 싱글톤을 예제 코드입니다. 객체의 생성을 관리할 수 있는 장점이 있다고 설명한 예제를 다시 보여드리는 이유는 바로 장점이라고 설명한 이 코드에 단점도 같이 있기 때문입니다. 그 단점은 바로 정적 팩토리 메서드를 적용하는 경우에는 상속을 이용한 확장이 불가능하다는 점입니다.

 

 정적 팩토리 메서드를 적용하게 되면 생성자를 private으로 선언하게 되는데, private 생성자를 가진 클래스는 상속을 통한 확장성을 가져갈 수가 없는 문제가 있습니다. 상속을 할 수 없으니 정적 팩토리 메서드를 적용한 클래스는 하위 클래스를 만들 수 없습니다. 재사용성 측면에서 좋지 않은 설계라고 말할 수 있는 부분입니다. 하지만 다른 측면에서 보면 일부로 확장이 불가능한 객체를 만드는 경우에는 유용할 수도 있습니다.

끝맺음

정적 팩토리 메서드(static factory method)의 역할은 단순히 생성자를 대신하는 용도로 사용되는게 아니라, 조금 더 가독성이 좋고 효율적으로 프로그래밍을 할 수 있게 도와주는 하나의 방법입니다.

반응형

댓글

Designed by JB FACTORY