정적 팩토리 메서드

박영준·2023년 3월 20일
0

Java

목록 보기
51/111

1. 정의

  • 생성자 기법과는 별도로 생성만을 담당하는 클래스 메소드

  • 직접적인 생성자가 아닌 메소드를 통해 객체를 생성

  • 외부에서 요청이 들어오면, 이에 맞게 객체 생성 or 단순 반환

참고: this, final, public static void main(String[] args) - 3) static

2. 장단점

장점

  1. 이름을 가질 수 있다
    예시 1

    // 생성자의 경우
    Public class Coke{
        public Coke(int sugar, int water){
            ...
        };
    }
    
    // 정적 팩토리 메서드의 경우
    public class Coke{
        public static Coke makeCokeBySugarAndWater(int sugar, int water){
            ...
        }
    }

    예시 2

    // 생성자의 경우
    class Book {
        private String title;
        private long isbn;
    
        Book(long isbn) {
            if (isbn == 9788966262281L) {
                this.isbn = isbn;
                this.title = "Effective Java 3/E";
            } else if (isbn == 9788998139766L) {
                this.isbn = isbn;
                this.title = "객체지향의 사실과 오해";
            }
        }
    
        Book(String title) {
            if (title.equals("Effective Java 3/E")) {
                this.title = title;
                this.isbn = 8966262287L;
            } else if (title.equals("객체지향의 사실과 오해")) {
                this.title = "객체지향의 사실과 오해";
                this.isbn = 9788998139766L;
            }
        }
    }
    
    // 정적 팩토리 메서드의 경우
    class Book {
        private String title;
        private long isbn;
    
        private Book(String title, long isbn) {
            this.title = title;
            this.isbn = isbn;
        }
    
        static Book createByIsbn(long isbn) {
            if (isbn == 9788966262281L) {
                return new Book("Effective Java 3/E", isbn);
            } else if (isbn == 9788998139766L) {
                return new Book("객체지향의 사실과 오해", isbn);
            }
    
            throw new IllegalArgumentException("일치하는 책이 없습니다.");
        }
    
        static Book createByTitle(String title) {
            if (title.equals("Effective Java 3/E")) {
                return new Book(title, 8966262287L);
            } else if (title.equals("객체지향의 사실과 오해")) {
                return new Book(title, 9788998139766L);
            }
    
            throw new IllegalArgumentException("일치하는 책이 없습니다.");
        }
    }
    // 생성자의 경우, 위의 코드 사용하기
    Book book1 = new Book(9788966262281L); // 이펙티브 자바 3판
    Book book2 = new Book("객체지향의 사실과 오해");
    
    // 정적 팩토리 메서드의 경우, 위의 코드 사용하기
    Book book1 = Book.createByIsbn(9788966262281L); // 이펙티브 자바 3판
    Book book2 = Book.createByTitle("객체지향의 사실과 오해");
    • 생성자의 경우, 기본적으로 이름을 가질 수 없다.
      정적 팩토리 메서드의 경우, 메소드로 객체를 생성하므로 이름 짓기의 자유도가 높다.

    • 해당 코드를 전달받은 다른 개발자는 원래 코드의 의도를 명확히 파악할 수가 없다.(해당 인자를 전달해야하는건지 등...)

  2. 변환에 있어 내부 구조를 캡슐화할 수 있다.

    // 인스턴스 메소드 사용시
    CarDto carDto = new CarDto(car.getCarName,car.getCarDistance)
    
    // 정적 팩토리 메소드 사용시
     CarDto carDto = CarDto.from(car)

    인스턴스 메소드를 사용할 경우, 생성자 자체와 그의 내부를 전부 노출시키게 되지만
    DTO 변환 시에 정적 팩토리 메소드를 사용할 경우, from 이나 of(외부객체를 해당 객체로 변환)를 사용하기만 하면 된다.

  3. 시그니처를 다양하게 사용할 수 있다.

    // 생성자
    public class Coke{
        public Coke();
        public Coke(int sugar);
        public Coke(int water);
    }
    
    // 정적 팩토리 메소드
    public class Coke{
        private Coke();
        public static Coke cokeOnlyWater(int water);
        public static Coke cokeOnlySugar(int sugar);
        public static Coke emptyCoke();
    }

    생성자의 경우
    매개변수의 개수가 정해지면 또 다른 생성자를 만들기가 애매하다.
    → 오버로딩을 통해 추가 생성할 수는 있으나, 이 경우 다른 개발자가 생성자를 이해하기가 어려워진다.
    → 도메인 바깥에서 보면 도메인 이름만 있고
    → 어떤 파라미터가 필요한지, 왜 생성자가 다중으로 있는지 ...등의 작성자 의도까지는 파악하기 어렵다. (도메인까지 가서 들여다봐야 한다)

    정적 팩토리 메소드의 경우
    다중 생성자가 각각의 메소드로 파지므로, 이름만 잘 지어주면 의도를 파악하기가 쉽다.

  4. 하위타입 반환 가능
    1) 해당 객체의 자식 객체를 생성/반환 가능
    → 관련된 대상들을 한 곳에 넣을 수 있고, 조건부로 자식 클래스 생성도 가능
    → 예시: 콜라 인터페이스 생성 --> 코카콜라, 펩시 생성 구현체 작성 --> 각 타입별로 반환 가능

    public interface Coke{
        public static Coke generateCocaCola(){
            return new CocaCola();
        }
    
        public static Coke generatePepsi(){
            return new Pepsi();
        }    
    }
    
    public class CocaCola implements Coke{
    }
    
    public class Pepsi implements Coke{
    }

    2) (인스턴스 클래스에서도 가능은 하지만) 정적 팩토리 메서드를 사용할 경우, 인터페이스의 실제 구현 클래스를 노출시키지 않고도 생성이 가능

    // 인스턴스 메소드 사용시 생성자 자체가 노출
    new CocaCola();
    
    // 정적 팩토리 메소드 사용시 실제 생성자(new)는 해당 메소드 안에 은닉되어있음. 
    Coke.generateCocaCola(); 

    3) 하위 타입에 맞기만 하면 되므로, 분기문 처리를 통해 반환하는 객체를 달리할수도 있다.

    public class Coke{
        ... 
        public Coke of(String name){
            if(name.equals("CocaCola"){
                return new CocaCola();
            }
            if(name.equals("Pepsi"){
                return new Pepsi();
            }
        }
    
        // 이런것도 가능하다.
        public Coke of(boolean sugar){
            return sugar?  new CocaCola() : new ZeroCola();
        }   
    }
  5. 호출할 때마다 새로운 객체를 생성할 필요가 없다.

    public class LottoNumber {
      private static final int MIN_LOTTO_NUMBER = 1;
      private static final int MAX_LOTTO_NUMBER = 45;
    
      private static Map<Integer, LottoNumber> lottoNumberCache = new HashMap<>();
    
      static {
        IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
                    .forEach(i -> lottoNumberCache.put(i, new LottoNumber(i)));
      }
    
      private int number;
    
      private LottoNumber(int number) {
        this.number = number;
      }
    
      public LottoNumber of(int number) {  // LottoNumber를 반환하는 정적 팩토리 메서드
        return lottoNumberCache.get(number);
      }
      ...
    }

    enum 처럼, 자주 사용되는 요소의 개수가 정해져있다면 해당 개수만큼 미리 생성해놓고 조회(캐싱)할 수 있는 구조로 만들 수 있다.

단점

  1. 상속 불가능
    정적 팩토리 메서드를 사용 할 경우, 생성자는 접근 제어자가 private 으로 한정되어 있으므로 해당 클래스는 부모 클래스가 될 수 없다.
    (단, 생성자 제어가 이점인 경우도 있음)

  2. (상대적으로) 생성자 찾기가 쉽지 않음
    인스턴스 클래스의 경우, 포맷이 생성자, 메소드1, 메소드2... 이런식으로 가는데
    정적 팩토리 메서드의 경우, 생성자도 일반적인 메소드와 같은 형식이므로 찾기가 어렵다.


참고: [Java] 정적 팩토리 메소드
참고: 정적 팩토리 메서드(Static Factory Method)는 왜 사용할까?
참고: 생성자 대신 정적 팩터리 메서드를 고려하라

profile
개발자로 거듭나기!

0개의 댓글