티스토리 뷰

정적 팩토리 메소드(static factory method)란?

보통 인스턴스를 생성하는 전형적인 수단은 public 생성자를 통한 인스턴스화이다. 하지만, 그 클래스의 인스턴스를 반환하는 정적 메소드로 인스턴스를 반환할 수 있다. 이것을 정적 팩토리 메소드라고 한다. static 메소드로 객체 생성을 캡슐화한다. 정적 팩토리 메소드는 디자인 패턴에 있는 팩소리 메소드 패턴과는 다르다.

// java BigInteger의 소스코드
public class BigInteger extends Number implements Comparable<BigInteger> {
    //...
    private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1];
    private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1];
    //...
    public static BigInteger valueOf(long val) {
        // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
        if (val == 0)
            return ZERO;
        if (val > 0 && val <= MAX_CONSTANT)
            return posConst[(int) val];
        else if (val < 0 && val >= -MAX_CONSTANT)
            return negConst[(int) -val];

        return new BigInteger(val);
    }
}

 

팩토리 메소드 패턴(Factory Method Design Pattern)이란?

객체를 만들어 내는 처리를 서브클래스로 분리하는 패턴이다. 클래스 간의 결합도를 낮춰 변화에 더 유연하게 대응하게 할 수 있다.

public abstract class Pizza {
    public abstract String getName();
}

public class CheesePizza extends Pizza {
    @Override
    public String getName() {
        return "Chee~~~se";
    }
}

public class SeafoodPizza extends Pizza {
    @Override
    public String getName() {
        return "Sea~~~food";
    }
}

public class PizzaFactory {
    public Pizza makePizza(String name) {
        if (name.equals("cheese")) {
            return new CheesePizza();
        }
        if (name.equals("seafood")) {
            return new SeafoodPizza();
        }
        return null;
    }
}

 

정적 팩토리 메소드의 장점

1. 생성자 역할을 하는 메소드에 이름(Naming)을 지을 수 있다.

특성을 잘 드러내는 이름을 지어 가독성을 높일 수 있다. 단순한 생성자로는 설명하기 어려운 특징을 이름으로 나타낼 수 있다.

생성자를 사용한다면 인스턴스를 만드는 방식을 다르게 하려면 다른 시그니처를 사용해야 한다. 하지만 정적 팩토리 메소드를 사용한다면 이름을 다르게 지어준다면 같은 입력 매개변수를 가져도 다른 방식으로 인스턴스 생성이 가능하다.

public class Car {
    private static final int CONDITION = 4;

    private final String team;
    private final String color;

    private int position;

    public Car(String team, int position, String color) {
        this.team = team;
        this.color = color;
        this.position = position;
    }

    public static Car teamCarFrom(String team){
        return new Car(team, 0, "Black");
    }

    public static Car leadCarFrom(int position){
        return new Car("A", position, "Black");
    }

    public static Car colorCarFrom(String color){
        return new Car("A", 0, color);
    }

}

2. 불필요한 인스턴스 생성을 피할 수 있고 인스턴스 통제가 가능하다.

불변 객체를 캐시하여 사용하고 있다면 굳이 new를 사용해서 새로운 인스턴스를 만들지 않아도 된다.

public class Position {
    private static final Position ZERO = new Position(0);
    private final int position;

    public Position(int position) {
        this.position = position;
    }

    public static Position getZeroPosition(){
        return ZERO;
    }
}

불변 클래스란?

대표적인 불편 클래스는 String이다. 클래스를 불변으로 만들려면 다음 다섯 가지 규칙을 따르면 된다.

  1. 객체 상태를 변경하는 메소드를 제공하지 않는다.
  2. 클래스를 확장할 수 없도록 한다.
  3. 모든 필드를 final 선언한다.
  4. 모든 필드를 private 선언한다.
  5. 자신 이외에는 가변 컴포넌트에 접근할 수 없도록 한다.

불변 객체는 생성~삭제까지 상태를 그대로 가지는 객체를 의미한다. 값이 변화된다면 불변 객체는 새로 생성되어야 한다. 해당 객체의 상태가 변하지 않기 때문에 안전하게 공유할 수 있다.

플라이 웨이트 패턴?

동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화하는 소프트웨어 디자인 패턴이다. pool을 사용하여 객체가 있다면 사용하고, 없다면 새로 만드는 방식이다. 가장 대표적인 것이 StringPool이다.

인스턴스 통제 방법

  • 싱글턴: 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 설계상 유일해야 하는 경우 필요하다. private static final로 하나의 인스턴스를 생성해두고, private 생성자로 인스턴스화를 막는다. 만들어진 1개의 인스턴스는 public 메소드로 그 인스턴스에 접근한다. 테스트 작성이 어려운 단점이 있다고 한다. 
    Enum 방식이 싱글턴을 만드는 가장 안전하고 좋은 방법이라고 한다.
  • 인스턴스화 불가 : private 생성자로 유틸리티 클래스에서 인스턴스 생성을 막을 수 있다. 

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

java.util.Collections에서는 인스턴스화 불가한 정적 팩토리 메소드를 사용하여 구현체를 얻을 수 있게 했다. 아래의 예시는 java.util.Collections 중 일부이다. static 메소드로 생성하고, 내부에 해당 클래스를 숨겨 간단한 API 메소드로 해당 클래스의 생성을 이용한다. 

    // java.util.Collections 소스코드
    public static <T> Set<T> unmodifiableSet(Set<? extends T> s) {
        return new UnmodifiableSet<>(s);
    }
    
    static class UnmodifiableSet<E> extends UnmodifiableCollection<E>
                                 implements Set<E>, Serializable {
        private static final long serialVersionUID = -9215047833775013803L;

        UnmodifiableSet(Set<? extends E> s)     {super(s);}
        public boolean equals(Object o) {return o == this || c.equals(o);}
        public int hashCode()           {return c.hashCode();}
    }

4. 입력 매개변수에 따라 매번 다른 클래스 객체를 반환 가능하다.

이펙티브 자바 책에서는 EnumSet이 예시로 나온다. EnumSet의 매개변수 갯수에 따라 다른 타입의 EnumSet 객체를 반환한다고 한다. 아래는 해당 내용을 보고 생각한 예시이다.

class Order {
    private static final int GROUP_ORDER = 30;

    public static Order order(int amount) {
        if (amount < GROUP_ORDER) {
            return new normalOrder(amount);
        }
        return new groupOrder(amount);
    }
}

5. 정적 팩터리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

위에서 작성한 Position 클래스를 사용하여 초기화하려고 할 때, Position의 인스턴스가 만들어져 있지 않아도, Position.getZeroPosition()으로 Position 클래스를 사용 가능하다.

public class Car{
    private Position position;

    public Car(String carNameValue) {
        this(carNameValue, Position.getZeroPosition());
    }
    
    public Car(String carNameValue, Position position) {
        this.carName = new CarName(carNameValue);
        this.position = position;
    }
    
}

정적 팩토리 메소드의 단점

1. 정적 팩토리 메소드만 제공하여 인스턴스를 생성하면 하위 클래스를 만들 수 없다.

public, protected 생성자 없이 정적 팩토리 메소드만 사용하면 하위 클래스(상속)을 사용할 수 없다. 상속이 되면 안되는 경우에는 장점으로 여겨질 수도 있다.

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

프로그래머가 정적 팩토리 메소드로 인스턴스화 하는 방법을 알기 어렵다. 따라서 흔히 사용하는 명명 방식을 사용하여 이름을 지어주는게 좋다.

흔히 사용하는 명명(Naming) 방식

  • from : 하나의 매개변수를 받아 해당 타입의 인스턴스 반환
  • of : 여러개의 매개변수를 받아 적합한 타입의 인스턴스 반환
  • valueOf : from과 of의 더 자세한 버전
  • instance 혹은 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스를 보장하진 않음
  • create 혹은 newInstance : 위와 같지만 매번 새로운 인스턴스를 반환함
  • getType : getInstance와 같지만 생성할 클래스가 아닌 다른 클래스에 팩터리 메소드를 정의할 때 사용 (Type은 반환할 객체의 타입)
  • newType : newInstance와 같지만 생성할 클래스가 아닌 다른 클래스에 팩터리 메소드를 정의할 때 사용 (Type은 반환할 객체의 타입)
  • Type : getType , newType 의 간결한 버전

정리

간단히 정리하면 정적 팩토리 메소드는 static으로 선언되어 객체를 반환하는 메소드이다. 인스턴스화를 하는데 있어 다양한 장점이 있어 사용된다.

 

수업시간에 나온 키워드를 더 알아보고 싶어서 해당 내용을 찾아보게 되었다. 이 내용을 이해하기 위해서는 너무 여러 개념이 섞여있어 이해하기가 어려웠다. 😅 중간에 나오는 개념들을 간단하게 정리하면서 같이 이해하려고 노력했다. 예시가 적절하지 않다거나 제대로 이해하고 있는 부분이 많을 수 있으니 있다면 코멘트 부탁드립니다.

References

이펙티브 자바(책) - 아이템 1, 3, 4, 17

팩토리 메소드 패턴 참고 블로그

정적 팩토리 메소드 참고 블로그

플라이 웨이트 패턴 참고 블로그

플라이웨이트 패턴 위키

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함