이펙티브 자바/ 아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라.

KimSongMok·2025년 4월 22일
0

아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라.

  1. 클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다.

    1. 예시
    public class Car {
        private String brand;
        public Car(String brand) {
            this.brand = brand;
        }
    }
    
    // 사용
    Car car = new Car("BMW");
  2. 하지만 클래스는 생성자와 별도로 정적 팩터리 메서드(static factory method)를 제공할 수 있다.
    → 그 클래스의 인스턴스를 반환하는 단순한 정적 메서드
    1. 예시

    ```java
    public class Car {
        private String brand;
    
        // private 생성자
        private Car(String brand) {
            this.brand = brand;
        }
    
        // 정적 팩터리 메서드
        public static Car createCar(String brand) {
            return new Car(brand);
        }
    }
    
    // 사용
    Car car = Car.createCar("BMW");
    ```
  3. 클래스는 클라이언트에 public 생성자 대신(혹은 생성자와 함께) 정적 팩터리 메서드를 제공할 수 있다. 이 방식에는 장점과 단점 모두 존재.

    1. 장점
      1. 이름을 가질 수 있다.
        1. 정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
          (생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다.)
          → 이름을 가질 수 있는 정적 팩터리 메서드에는 (하나의 시그니처로 생성자를 하나만 만들 수 있는) 이러한 제약이 없다.
          ```java
          //생성자 제약 예시
          public class User {
              private String name;
              private boolean isAdmin;
          
              // 생성자: 일반 사용자와 관리자 구분
              public User(String name, boolean isAdmin) {
                  this.name = name;
                  this.isAdmin = isAdmin;
              }
          
              @Override
              public String toString() {
                  return "User{name='" + name + "', isAdmin=" + isAdmin + "}";
              }
          
              public static void main(String[] args) {
                  // 일반 사용자 생성
                  User regularUser = new User("Alice", false);
          
                  // 관리자 생성
                  User adminUser = new User("Bob", true);
          
                  System.out.println(regularUser); // 출력: User{name='Alice', isAdmin=false}
                  System.out.println(adminUser);   // 출력: User{name='Bob', isAdmin=true}
              }
          }
          ```
          
          ```java
          // 정적 팩터리 메서드에서 생성자의 한계 극복 예시
          public class User {
              private String name;
              private boolean isAdmin;
          
              // private 생성자
              private User(String name, boolean isAdmin) {
                  this.name = name;
                  this.isAdmin = isAdmin;
              }
          
              // 정적 팩터리 메서드: 일반 사용자 생성
              public static User createRegularUser(String name) {
                  return new User(name, false);
              }
          
              // 정적 팩터리 메서드: 관리자 생성
              public static User createAdminUser(String name) {
                  return new User(name, true);
              }
          
              @Override
              public String toString() {
                  return "User{name='" + name + "', isAdmin=" + isAdmin + "}";
              }
          
              public static void main(String[] args) {
                  // 정적 팩터리 메서드를 사용한 객체 생성
                  User regularUser = User.createRegularUser("Alice");
                  User adminUser = User.createAdminUser("Bob");
          
                  System.out.println(regularUser); // 출력: User{name='Alice', isAdmin=false}
                  System.out.println(adminUser);   // 출력: User{name='Bob', isAdmin=true}
              }
          }
          ```
          
          **차이점 요약**
          
          1. **생성자의 제약**
              1. 동일한 시그니처를 가진 추가 생성자를 정의할 수 없음.
              2. 매개변수(false, true)의 의미를 코드만으로 명확히 알기 어려움.
          2. **정적 팩터리 메서드의 장점**
              1. createRegularUser, createAdminUser처럼 이름으로 객체의 특성을 설명 가능.
              2. 가독성이 높아지고, 반환되는 객체의 의미를 명확히 전달 가능.
      2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
        1. 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용

          1. 대표적 예인 Boolean.valueOf(boolean) 메서드 → 객체 생성X

            public static Boolean valueOf(boolean b){
            	return b? Boolean.TRUE : Boolean.FALSE;
            	}
            	Integer
            	String
            	Double
            	Long
          2. 특히 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능이 상당히 끌어올려준다.

        2. 플라이웨이트(Flyweight Pattern) 패턴도 이와 비슷한 기법이라 할 수 있다.

          1. 플라이웨이트 패턴? → 객체의 개수를 줄여 메모리를 절약하기 위해 사용하는 구조적 디자인 패턴
            공유 객체를 사용하여 동일하거나 비슷한 객체를 중복 생성하지 않고 재사용하도록 함.
            1. 장점
            1. 메모리 사용량 감소
            2. 객체 생성 비용 절감
            2. 단점
            1. 코드 복잡성 증가: 상태를 내부와 외부로 나누고, 객체 공유를 관리해야 하므로 코드가 복잡해질 수 있음
          import java.util.HashMap;
          import java.util.Map;
          
          // 플라이웨이트 인터페이스: 공유 객체의 공통 동작 정의
          interface Flyweight {
              void display(int x, int y); // 외부 상태를 받아서 객체를 동작시킴
          }
          
          // ConcreteFlyweight: 공유 객체의 실제 구현
          class Character implements Flyweight {
              private final char symbol;  // 내부 상태 (공유 가능한 고정 데이터)
              private final String font; // 내부 상태 (글꼴 정보)
          
              public Character(char symbol, String font) {
                  this.symbol = symbol;
                  this.font = font;
              }
          
              @Override
              public void display(int x, int y) {
                  // 외부 상태(x, y 좌표)에 따라 달라지는 행동
                  System.out.println("Character: " + symbol + ", Font: " + font + ", Position: (" + x + ", " + y + ")");
              }
          }
          
          // Flyweight Factory: 공유 객체를 관리하고 생성
          class FlyweightFactory {
              private final Map<String, Flyweight> flyweights = new HashMap<>();
          
              // Flyweight 객체를 반환 (필요하면 생성)
              public Flyweight getCharacter(char symbol, String font) {
                  String key = symbol + font; // 공유 객체를 고유하게 구분할 키
                  if (!flyweights.containsKey(key)) {
                      // 객체가 없으면 새로 생성
                      flyweights.put(key, new Character(symbol, font));
                      System.out.println("Created new Flyweight: " + key);
                  }
                  return flyweights.get(key); // 기존 객체 반환
              }
          }
          
          // 클라이언트 코드
          public class FlyweightPatternExample {
              public static void main(String[] args) {
                  FlyweightFactory factory = new FlyweightFactory();
          
                  // 'A' 문자 객체 생성 및 재사용
                  Flyweight a1 = factory.getCharacter('A', "Arial");
                  Flyweight a2 = factory.getCharacter('A', "Arial"); // 이미 생성된 객체 재사용
          
                  // 'B' 문자 객체 생성
                  Flyweight b = factory.getCharacter('B', "Arial");
          
                  // 객체 동작 (외부 상태: x, y 좌표)
                  a1.display(10, 20);  // 출력: Character: A, Font: Arial, Position: (10, 20)
                  a2.display(30, 40);  // 출력: Character: A, Font: Arial, Position: (30, 40)
                  b.display(50, 60);   // 출력: Character: B, Font: Arial, Position: (50, 60)
          
                  // 동일 객체 재사용 여부 확인
                  System.out.println(a1 == a2); // true (같은 객체를 공유)
              }
          }
        3. 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다.

          1. 이러한 클래스를 인스턴스 통제 클래스라고 함
          2. 사용 이유?
            1. 싱글턴으로 만들 수 있음
            2. 인스턴스화 불가로 만들 수 있음
            3. 불변 값클래스에서 동치(equal)인 인스턴스가 단 하나뿐임을 보장할 수 있음
          import java.util.HashMap;
          import java.util.Map;
          
          // 인스턴스 통제 클래스: 정적 팩터리 메서드를 사용하여 객체를 통제
          public class InstanceControlledClass {
          
              // 내부적으로 관리되는 Map (불변 값 클래스를 위한 예시)
              private static final Map<String, InstanceControlledClass> instances = new HashMap<>();
          
              private final String value; // 불변 값 (내부 상태)
          
              // private 생성자: 외부에서 직접 객체 생성 불가
              private InstanceControlledClass(String value) {
                  this.value = value;
              }
          
              // 정적 팩터리 메서드: 동일한 값의 인스턴스는 단 하나만 존재하도록 통제
              public static InstanceControlledClass getInstance(String value) {
                  // 이미 존재하는 객체가 있으면 반환, 없으면 새로 생성
                  instances.computeIfAbsent(value, InstanceControlledClass::new);
                  return instances.get(value);
              }
          
              // 값 반환 메서드
              public String getValue() {
                  return value;
              }
          
              @Override
              public String toString() {
                  return "InstanceControlledClass{" + "value='" + value + '\'' + '}';
              }
          
              public static void main(String[] args) {
                  // 동일한 값으로 객체 생성 요청
                  InstanceControlledClass obj1 = InstanceControlledClass.getInstance("A");
                  InstanceControlledClass obj2 = InstanceControlledClass.getInstance("A");
                  InstanceControlledClass obj3 = InstanceControlledClass.getInstance("B");
          
                  // 출력
                  System.out.println(obj1); // InstanceControlledClass{value='A'}
                  System.out.println(obj2); // InstanceControlledClass{value='A'}
                  System.out.println(obj3); // InstanceControlledClass{value='B'}
          
                  // 동일한 값 객체는 동일성을 보장
                  System.out.println(obj1 == obj2); // true (동일 객체)
                  System.out.println(obj1 == obj3); // false (다른 객체)
              }
          }
      3. 반환 타임의 하위 타입 객체를 반환할 수 있는 능력이 있다.
        1. 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성을 가짐
          1. API를 만들때 이 유연성을 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지 할 수 있다
            → 인터페이스를 정적 팩터리 메서드의 반환 타임으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술

            ```java
            // 인터페이스: 공개 API의 일부
            interface Shape {
                void draw();
            }
            
            // 구현 클래스 1: 구체적인 구현 (Circle)
            class Circle implements Shape {
                private final double radius;
            
                public Circle(double radius) {
                    this.radius = radius;
                }
            
                @Override
                public void draw() {
                    System.out.println("Drawing a Circle with radius: " + radius);
                }
            }
            
            // 구현 클래스 2: 구체적인 구현 (Rectangle)
            class Rectangle implements Shape {
                private final double width;
                private final double height;
            
                public Rectangle(double width, double height) {
                    this.width = width;
                    this.height = height;
                }
            
                @Override
                public void draw() {
                    System.out.println("Drawing a Rectangle with width: " + width + " and height: " + height);
                }
            }
            
            // 정적 팩터리 메서드 제공 클래스
            class ShapeFactory {
                // 정적 팩터리 메서드: 인터페이스 타입으로 객체를 반환
                public static Shape createCircle(double radius) {
                    return new Circle(radius); // Circle 객체를 반환
                }
            
                public static Shape createRectangle(double width, double height) {
                    return new Rectangle(width, height); // Rectangle 객체를 반환
                }
            }
            
            // 클라이언트 코드
            public class InterfaceBasedFactoryExample {
                public static void main(String[] args) {
                    // Circle 객체 생성 (Shape 인터페이스 타입으로 반환됨)
                    Shape circle = ShapeFactory.createCircle(5.0);
            
                    // Rectangle 객체 생성 (Shape 인터페이스 타입으로 반환됨)
                    Shape rectangle = ShapeFactory.createRectangle(10.0, 20.0);
            
                    // 인터페이스를 통해 메서드 호출
                    circle.draw();       // 출력: Drawing a Circle with radius: 5.0
                    rectangle.draw();    // 출력: Drawing a Rectangle with width: 10.0 and height: 20.0
                }
            }
            ```
          2. 자바 8전에는 인터페이스에 정적 메서드를 선언할 수 없었다.

            // Java 8 이전: 유틸리티 클래스를 사용한 정적 메서드
            public class MathUtils {
                private MathUtils() {
                    // 인스턴스화 방지
                }
            
                public static int add(int a, int b) {
                    return a + b;
                }
            
                public static int multiply(int a, int b) {
                    return a * b;
                }
            }
          3. 자바 8부터 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸다.

            1. 인스턴스화 불가 동반 클래스를 둘 이유가 별로 없다.
            2. 동반 클래스에 두었던 public 정적 멤버들 상당수를 그냥 인터페이스 자체에 두면 된다
            3. 정적 메서드를 구현하기 위한 코드 중 많은 부분은 여전히
              별도의 package-private클래스에 두어야 할 수 있다.(자바 8에서도 public 정적 멤버만 허용하기 때문)
            4. 자바 9에서는 private 정적 메서드까지 허락하지만 정적 필드와 정적 멤버 클래스는 여전히 public이어야 한다.
            // 인터페이스: 정적 메서드를 포함할 수 있음
            public interface MathUtils {
            
                // Java 8: 인터페이스에 정적 메서드 선언 가능
                static int add(int a, int b) {
                    return a + b;
                }
            
                static int multiply(int a, int b) {
                    return a * b;
                }
            
                // Java 9: private 정적 메서드 (재사용을 위해)
                private static void validateInput(int a, int b) {
                    if (a < 0 || b < 0) {
                        throw new IllegalArgumentException("Inputs must be non-negative.");
                    }
                }
            
                // Java 9: private 정적 메서드 호출 예제
                static int subtract(int a, int b) {
                    validateInput(a, b); // private 정적 메서드 호출
                    return a - b;
                }
            }
            
            // 클라이언트 코드
            public class StaticMethodInInterfaceExample {
                public static void main(String[] args) {
                    // Java 8: 인터페이스의 정적 메서드 호출
                    int sum = MathUtils.add(5, 10);  // 15
                    int product = MathUtils.multiply(5, 10); // 50
            
                    // Java 9: private 정적 메서드를 사용하는 subtract
                    int difference = MathUtils.subtract(15, 5); // 10
            
                    // 출력
                    System.out.println("Sum: " + sum);           // Sum: 15
                    System.out.println("Product: " + product);   // Product: 50
                    System.out.println("Difference: " + difference); // Difference: 10
                }
            }
      4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
        1. 반환 타임의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
          → 다음 릴리스에서 또 다른 클래스의 객체를 반환해도 된다.
          ex) EnumSet 클래스는 public 생성자 없이 오직 정적 팩터리만 제공
          OpenJDK에서는 원소(Enum 타입의 값 → 열거형 상수)의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 반환함
          1. 원소 64개 이하 → long 변수 하나로 관리(RegularEnumSet)
          2. 원소 65개 이상 → long 배열로 관리(JumboEnumSet)
          클라이언트는 이 두 클래스의 존재를 모른다.
          만약, 원소가 적을 때, RegularEnumSet을 사용할 이점이 없어진다면, 다음 릴리스 때는 이를 삭제해도 아무문제가 없다.
          
          비슷하게 성능을 더 개선한 3,4 번째 클래스를 다음 릴리스에 추가 할 수 있다.
          → 클라이언트는 팩터리가 건네주는 객체가 어느 클래스의 인스턴스인지 알 수도 없고, 알 필요도 없다. EnumSet의 하위 클래스이기만 하면 된다.
          
          ```java
          import java.util.*;
          
          // Enum 타입: 열거형 상수
          enum Day {
              MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
          }
          
          // EnumSet의 상위 클래스 (공개된 API)
          abstract class CustomEnumSet<E extends Enum<E>> {
              // 정적 팩터리 메서드
              public static <E extends Enum<E>> CustomEnumSet<E> of(EnumSet<E> elements) {
                  if (elements.size() <= 64) {
                      return new RegularEnumSet<>(elements); // 64개 이하일 때
                  } else {
                      return new JumboEnumSet<>(elements);  // 65개 이상일 때
                  }
              }
          
              // 추상 메서드: 하위 클래스에서 구현
              public abstract void displayElements();
          }
          
          // RegularEnumSet: 64개 이하의 원소를 효율적으로 관리
          class RegularEnumSet<E extends Enum<E>> extends CustomEnumSet<E> {
              private final EnumSet<E> elements;
          
              RegularEnumSet(EnumSet<E> elements) {
                  this.elements = elements;
              }
          
              @Override
              public void displayElements() {
                  System.out.println("RegularEnumSet: " + elements);
              }
          }
          
          // JumboEnumSet: 65개 이상의 원소를 관리
          class JumboEnumSet<E extends Enum<E>> extends CustomEnumSet<E> {
              private final EnumSet<E> elements;
          
              JumboEnumSet(EnumSet<E> elements) {
                  this.elements = elements;
              }
          
              @Override
              public void displayElements() {
                  System.out.println("JumboEnumSet: " + elements);
              }
          }
          
          // 클라이언트 코드
          public class EnumSetExample {
              public static void main(String[] args) {
                  // EnumSet 생성 (원소 64개 이하)
                  EnumSet<Day> smallSet = EnumSet.of(Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY);
          
                  // EnumSet 생성 (원소 65개 이상을 위해 더미 데이터로 확장)
                  EnumSet<Day> largeSet = EnumSet.allOf(Day.class);
          
                  // 정적 팩터리 메서드로 CustomEnumSet 생성
                  CustomEnumSet<Day> smallEnumSet = CustomEnumSet.of(smallSet);
                  CustomEnumSet<Day> largeEnumSet = CustomEnumSet.of(largeSet);
          
                  // 원소 출력
                  smallEnumSet.displayElements(); // 출력: RegularEnumSet: [MONDAY, TUESDAY, WEDNESDAY]
                  largeEnumSet.displayElements(); // 출력: JumboEnumSet: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
              }
              
              /*
              출력 결과
          		RegularEnumSet: [MONDAY, TUESDAY, WEDNESDAY]
          		JumboEnumSet: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
              */
          }
          ```
          
          1. **정적 팩터리 메서드의 유연성**
              1. 입력 매개변수에 따라 다른 클래스 객체를 반환할 수 있음.
              2. 반환 타입의 하위 클래스이기만 하면 어떤 클래스 객체를 반환하든 클라이언트 코드에는 영향을 주지 않음.
          2. **구현 세부사항 캡슐화**
              1. 클라이언트는 내부적으로 어떤 클래스가 사용되는지 알 필요가 없음.
              2. 내부 구현이 변경되어도 클라이언트 코드는 수정할 필요가 없음.
          3. **미래 확장성**
              1. 새로운 요구사항이나 최적화가 필요할 때, 새로운 하위 클래스를 추가하거나 기존 클래스를 대체 가능.
      5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
        1. 서비스 제공자 프레임워크를 만드는 근간
          ex) JDBC
          1. ‘서비스 제공자 프레임워크’에서의 ‘제공자’는 서비스의 구현체
          2. 이 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 통제, 클라이언트를 구현체로부터 분리

          **쉽게 설명한다면, JDBC가 “서비스 제공자 프레임워크”의 대표적 예시인 이유는**
          **JDBC는** **클라이언트가 직접 데이터베이스 드라이버를 알 필요 없이, 프레임워크가 적절한 드라이버를 찾아 연결을 만들어주기 때문이다.**
          
          JDBC는 **데이터베이스와 클라이언트를 연결하는 서비스 제공자 프레임워크**.
          
          1. **프레임워크**: DriverManager
              1. 클라이언트의 요청에 따라 적절한 데이터베이스 드라이버를 찾아서
              연결을 제공한다
          2. **서비스 제공자**: 데이터베이스 드라이버
              1. MySQL, PostgreSQL, H2 등 다양한 데이터베이스 드라이버가 서비스 제공자 역할을 한다.
          3. **클라이언트**: 애플리케이션 개발자
              1. 클라이언트는 단순히 데이터베이스 URL, 사용자 이름, 비밀번호를 DriverManager에 전달하면, 적절한 드라이버가 선택되어 연결을 제공한다.
              2. 어떤 드라이버가 사용되는지는 몰라도 된다.
        2. 서비스 제공자 프레임워크는 ‘3개의 핵심 컴포넌트’로 이루어짐

          1. 서비스 인터페이스
            1. 구현체의 동작을 정의 → 클라이언트가 사용할 공통 동작을 정의.
          2. 제공자 등록 API
            1. 제공자가 구현체를 등록할 때 사용하는 API
          3. 서비스 접근 API
            1. 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API

          클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있다.

          조건명시 x시, 기본 구현체 or 지원하는 구현체들을 하나씩 돌아가며 반환

          → 유연한 정적 팩터리의 실체

          3개의 핵심 컴포넌트와 더불어 종종 ‘서비스 제공자 인터페이스’라는 네 번째 컴포넌트가 쓰이기도 함.
          역할: 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명해줌.

          // 1. 서비스 인터페이스: 음악 스트리밍 서비스의 공통 동작 정의
          interface MusicService {
              void play(String songName);
              void stop();
          }
          
          // 2. 제공자 구현체: Spotify 구현체
          class SpotifyService implements MusicService {
              @Override
              public void play(String songName) {
                  System.out.println("Playing '" + songName + "' on Spotify.");
              }
          
              @Override
              public void stop() {
                  System.out.println("Stopping Spotify.");
              }
          }
          
          // 2. 제공자 구현체: YouTube Music 구현체
          class YouTubeMusicService implements MusicService {
              @Override
              public void play(String songName) {
                  System.out.println("Playing '" + songName + "' on YouTube Music.");
              }
          
              @Override
              public void stop() {
                  System.out.println("Stopping YouTube Music.");
              }
          }
          
          // 3. 제공자 등록 API:
          import java.util.HashMap;
          import java.util.Map;
          
          // 제공자 관리 및 등록
          class MusicServiceRegistry {
              private static final Map<String, MusicService> providers = new HashMap<>();
          
              // 제공자 등록
              public static void registerProvider(String name, MusicService provider) {
                  providers.put(name, provider);
              }
          
              // 서비스 접근 API
              public static MusicService getService(String name) {
                  MusicService service = providers.get(name);
                  if (service == null) {
                      throw new IllegalArgumentException("No music service found with name: " + name);
                  }
                  return service;
              }
          
              // 기본 제공자 반환
              public static MusicService getDefaultService() {
                  if (providers.isEmpty()) {
                      throw new IllegalStateException("No music services registered.");
                  }
                  return providers.values().iterator().next();
              }
          }
          
          //4. 클라이언트 코드
          public class MusicServiceExample {
              public static void main(String[] args) {
                  // 제공자 등록
                  MusicServiceRegistry.registerProvider("Spotify", new SpotifyService());
                  MusicServiceRegistry.registerProvider("YouTubeMusic", new YouTubeMusicService());
          
                  // 특정 제공자 사용
                  MusicService spotify = MusicServiceRegistry.getService("Spotify");
                  spotify.play("Shape of You"); // 출력: Playing 'Shape of You' on Spotify.
                  spotify.stop();              // 출력: Stopping Spotify.
          
                  // 기본 제공자 사용
                  MusicService defaultService = MusicServiceRegistry.getDefaultService();
                  defaultService.play("Bohemian Rhapsody"); // 출력: Playing 'Bohemian Rhapsody' on Spotify.
              }
          }

          코드 설명

          1. 서비스 인터페이스
            1. MusicService는 모든 음악 스트리밍 서비스가 따라야 할 규칙(재생, 정지)을 정의
          2. 제공자 등록 API
            1. MusicServiceRegistry.registerProvider를 사용해 SpotifyService와 YouTubeMusicService를 등록함.
            2. 새로운 음악 서비스가 추가된다면 이 API를 통해 간단히 등록 가능하다.
          3. 서비스 접근 API
            1. getService("Spotify")를 사용하면 Spotify 서비스 구현체를 반환한다.
            2. 조건 없이 호출할 경우 getDefaultService를 통해 첫 번째 등록된 서비스를 반환한다.
    2. 단점
      1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다

        1. 컬렉션 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다.
        2. 상속 보다 컴포지션 사용 하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야한다.(장점으로 작용할 수 있음)
      2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

        1. 생성자처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야한다.
          → API 문서 잘 쓰기, 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해줘야 한다.

        → 그래서 쓰는 정적 팩터리 메서드에서 흔히 사용하는 명명 방식:

        1. from
          1. 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환.
          2. 예제: Date d = Date.from(instant);
        2. of
          1. 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환.
          2. 예제: Set faceCards = EnumSet.of(JACK, QUEEN, KING);
        3. valueOf
          1. from과 유사하지만, 더 의미 있는 이름을 제공.
          2. 예제: BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
        4. instance 또는 getInstance
          1. 매개변수 유무와 상관없이 특정 인스턴스를 반환.
          2. 예제: StackWalker luke = StackWalker.getInstance(options);
        5. create 또는 newInstance
          1. 새 인스턴스를 생성해 반환.
          2. 예제: Object newArray = Array.newInstance(classObject, arrayLen);
        6. getType
          1. getInstance와 비슷하지만, 반환 객체의 타입을 명확히 설명.
          2. 예제: FileStore fs = Files.getFileStore(path);
        7. newType
          1. newInstance와 비슷하지만, 반환 객체의 타입을 명확히 설명.
          2. 예제: BufferedReader br = Files.newBufferedReader(path);
        8. type
          1. getType 또는 newType에서 “get”이나 “new”를 생략한 간단한 형태.
          2. 예제: List litany = Collections.list(legacyLitany);

핵심 정리:

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

profile
최고가 되고싶은 개발자.

0개의 댓글