[Java] 정적 팩토리 메서드

simhani1·2023년 12월 28일

Java

목록 보기
1/3
post-thumbnail

서론

백기선님의 유튜브를 보다가 정적 팩토리 메서드 소개 영상을 보게 되었고 내용을 정리하고자 합니다.

개념

한 문장으로 요약하자면 객체 생성의 역할을 하는 클래스 메서드입니다. static 메서드를 통해 간접적으로 생성자를 호출하여 인스턴스를 생성할 수 있습니다.

정적 팩토리 메서드의 차별점

우선 기존 생성자와 다른 점은 아래와 같습니다.

  1. 이름을 가질 수 있다.
  2. 인스턴스를 통제하고 관리할 수 있다.
  3. 하위 자료형 객체를 반환할 수 있다.

[1] 이름을 가질 수 있다.

보통 객체를 생성할 때 new연산자를 사용합니다. 그러나 new연산자를 사용하여 객체를 생성하기 위해서는 객체 내부 구조를 잘 파악해야 합니다.

예제 코드로 살펴보겠습니다.

public class Book {

    private String name;
    private String author;
    private boolean sales;

    public Book(String name, String author, boolean sales) {
        this.name = name;
        this.author = author;
        this.sales = sales;
    }
}
public static void main(String[] args) {
    Book notForSale = new Book("판매하지 않을 책", "심작가", false);
    Book forSale = new Book("판매할 책", "심작가", true);
}

객체 생성 시점에 마지막 매개변수가 판매 여부를 결정하고 있다는 사실을 알고 있어야 합니다. 만약 매개변수의 의미를 제대로 파악하지 못하면 잘못된 객체를 생성할 수도 있습니다. 또한 생성자 코드를 보고 구체적으로 어떤 상태의 객체를 만드는 것인지 파악하기 어려운 문제가 있습니다.

이때 정적 팩토리 메소드를 사용하여 객체 생성 목적을 메소드 이름으로 표현해볼 수 있습니다.

public class Book {

    private String name;
    private String author;
    private boolean sales;

    private Book(String name, String author, boolean sales) {
        this.name = name;
        this.author = author;
        this.sales = sales;
    }

    public static Book notForSale(String name, String author) {
        return new Book(name, author, false);
    }

    public static Book forSale(String name, String author) {
        return new Book(name, author, true);
    }
}
public static void main(String[] args) {
    Book notForSale = Book.notForSale("책", "심작가");
    Book forSale = Book.forSale("책", "심작가");
}

메서드 이름을 통해 어떤 객체를 생성하는지 바로 파악이 됩니다. 이처럼 정적 팩토리 메소드를 사용하면 가독성이 좋아지고, 외부에서 생성자를 함부로 사용할 수 없도록 제어할 수 있습니다.

[2] 인스턴스를 통제하고 관리할 수 있다.

생성자를 외부로 노출하지 않기 때문에 객체 생성을 제어할 수 있습니다. 대표적인 예시로 싱글톤 패턴이 있습니다.

public class Book {

    private String name;
    private String author;
    private boolean sales;

    private Book(String name, String author, boolean sales) {
        this.name = name;
        this.author = author;
        this.sales = sales;
    }

    private static final Book BOOK = new Book("책", "심작가", true);

    public static Book getInstance() {
        return BOOK;
    }
}
public static void main(String[] args) {
    Book book1 = Book.getInstance();
    Book book2 = Book.getInstance();
    Book book3 = Book.getInstance();
    System.out.println("book1 = " + book1);
    System.out.println("book2 = " + book2);
    System.out.println("book3 = " + book3);
}

싱글톤 패턴에서는 미리 생성된 인스턴스를 반환하는 static 메서드만 제공합니다. 덕분에 임의로 새로운 객체를 생성할 수 없고, 하나의 객체를 공유하여 사용하게 됩니다.

추가로 인스턴스에 대한 캐싱이 가능하며 이를 플라이 웨이트 패턴이라고 합니다.

public class Color {

    private String color;

    public Color(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }
}
class ColorFactory {

    private static final Map<String, Color> cache = new HashMap<>();

    static {
        cache.put("블루", new Color("블루"));
        cache.put("레드", new Color("레드"));
        cache.put("그린", new Color("그린"));
    }

    // 정적 팩토리 메서드
    public static Color of(String code) {
        if(cache.containsKey(code)) {
            System.out.println("캐싱되어 있는 색깔입니다.");
            return cache.get(code);
        } else {
            System.out.println("캐싱되어 있지 않은 색깔입니다.");
            Color newColor = new Color(code);
            cache.put(code, newColor);
            return newColor;
        }
    }
}
public static void main(String[] args) {
    Color blue = ColorFactory.of("블루");
    Color brown = ColorFactory.of("브라운");
    Color brown2 = ColorFactory.of("브라운");
    System.out.println("blue = " + blue);
    System.out.println("brown = " + brown);
    System.out.println("brown2 = " + brown2);
}

인스턴스를 캐싱하여 저장하고 재사용하는 패턴입니다. 이렇게 인스턴스의 생성에 관여하고 통제 역할을 하는 ColorFactory 클래스를 인스턴스 통제 클래스라고 합니다.

[3] 하위 자료형 객체를 반환할 수 있다.

Level클래스를 상속받는 Basic, Intermediate, Advanced 클래스가 있습니다. 점수에 따라 하위 등급 타입을 결정해야 하는 경우, 정적 팩토리 메소드를 사용하여 구현 가능합니다.

public class Level {

    public static Level of(int score) {
        if (score < 50) {
            return new Basic();
        } else if (score < 80) {
            return new Intermediate();
        } else {
            return new Advanced();
        }
    }
}

정적 팩토리 메소드의 단점

  1. 상속 불가능
  2. 문서화를 할 때 정적 팩토리 메소드를 찾기 어렵다.

[1] 상속 불가능

정적 팩토리 메소드를 사용하면서 생성자의 접근 제어자를 public으로 설정하지 않습니다. 따라서 해당 클래스를 상속받을 때 부모 생성자 super()를 호출할 수 없는 한계가 있습니다.

public class ComplexBook extends Book {

    ...
    public ComplexBook() {
        super("name", "author", true);  // 불가능
        ...
    }
}

그러나 이러한 제약을 합성관계를 통해 우회할 수 있습니다.

public class ComplexBook {

    private Book book;  // 필드로 Book을 갖는다.

    public String getName() {
        return book.getName();  // book의 기능을 사용한다.
    }
    ...
}

[2] 문서화할 때 정적 팩토리 메소드를 찾기 어렵다.

javadoc을 작성하여 정적 팩토리의 역할에 대해 정리를 하면 메서드의 기능을 더욱 쉽게 파악할 수 있습니다.

마무리

인스턴스 생성 방법에 의미를 부여하고 통제할 필요가 있는 경우 정적 팩토리 메서드 도입을 고려하면 좋을 것 같습니다.

참고

0개의 댓글