거꾸로 바라본 세상
반응형

규칙 1. 생성자대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라



클래스를 통해 객체를 만드는 일반적인 방법

- public으로 선언된 생성자(constructor)

 

다른 방법

클래스에 public으로 선언된 정적 팩터리 메서드(static factory method) 추가하는 .

Ex)_Boolean 클래스의 valueOf

 

정적 팩터리 메서드의 장점


1. 생성자와는 달리 정적 팩터리 메서드에는 이름(name) 있다.

- 생성자에 전달되는 인자(parameter)들은 어떤 객체가 생성되는지를 설명하지 못함.

- 정적 팩터리 메서드는 이름을 짓기만하면 사용하기 쉽고, 클라이언트 코드의 가독성(readability) 높아짐

- 클래스에는 시그니처(signature)별로 하나의 생성자만 넣을 있는데 제약을 피하는 방법은 인자의 순서를 바꾸는 것이다. 그래서 생성자의 용도를 절대로 기억하지 못할 것이고, 실수로 엉뚱한 생성자를 호출하게 있다.

- 정적 펙터리 메서드는 이름(name) 있으므로 시그니처별로 하나의 생성자만 넣을 필요가 없다.

 

2 . 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요가 없다.


- 변경 불가능 클래스라면 이미 만들어둔 객체를 활용할 있고 객체가 불필요하게 생성하는 일을 피할 있다.

- 기법은 경량(Flyweight)패턴과 유사하므로 동일한 객체가 요청되는 일이 잦고, 특히 객체를 만드는 비용이 적용하면 크게 개선이 가능.

** 객체 통제 클래스(instance-controlled class) 작성하는 이유

- 개체 수를 제어하면 싱글턴 패턴을 따르도록 있고, 객체 생성이 불가능한 클래스를 만들 있다.

 

3. 생성자와는 달리 반환값 자료형의 하위 자료형 객체를 반환할 있다.


- 그래서 반환되는 객체의 클래스를 훨씬 유연하게 결정할 있다. 유연성을 활용하면 public 으로 선언되지 않은 클래스의 객체를 반환하는 API 만들 있다. 그러면 구현 세부사항을 감출 있으므로 아주 간결한 API 가능하다.   기법은 인터페이스기반 프레임워크(interface-based framework)구현에 적합하다. 인터페이스는 정적 메서드를 가질 없으므로, 관습상 반환값 자료형이 Type이라는 이름의 인터페이스인 정적 펙토리 메서드는 Types 라는 이름의 객체 생성불가능 클래스 안에 둔다.


4. 형인자 자료형 (parameterized type)객체를 만들 때 편하다.

 

- 이런 클래스의 생성자를 호출할 때는, 문맥상 형인자가 명백하더라도 반드시 인자로 형인자를 전달해야한다. 그래서 보통 형인자는 연달아 두 번 사용하게 된다.

Map<String, List<String>> m = new HashMap<String, List<String>>();

이처럼 자료형 명세를 중복하면, 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어진다.


하지만 정적 펙터리 메서드를 사용하면 컴파일러가 형인자를 스스로 알아내도록 할 수 있다. 이런 기법을 자료형 유추(type interface)라고 부른다.

ex) HashMap 클래스가 아래의 제네릭 정적 팩터리 메서드를 제공한다고 가정

public static <K, V> HashMap<K, V> newInstance() {

return new HashMap<K, V>();

}

이런 메서드가 있으면 앞서 살펴본 선언문을 좀더 간결하게 작성할 수 있다.

Map<String, List<String>> m = HashMap.newInstance();


** 서비스제공자 프레임워크

1.       서비스 인터페이스 : 서비스 제공자가 구현

2.       제공자 등록 API : 구현체를 시스템이 등록하여 클라이언트가 있도록

3.       서비스 접근  API : 클라이언트에서 실제 서비스 구현체를 제공

4.        서비스 제공자 인터페이스 : (옵션) 서비스 제공자가 구현.  서비스 제공자 인터페이스가 없는경우 구현체는 클래스 이름으로 등록되며 자바의 리플렉션(reflection)기능을 통해 객체로 만들어진다.

Ex)


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
32
33
34
35
36
37
38
39
40
41
42
43
//서비스 인터페이스
public interface Service {
    // 서비스에 대한 고유한 메서드들이 이 자리에 옴
}
 
 
//서비스 제공자 인터페이스
public interface Provider {
    Service newService();
}
 
//서비스 등록과 접근에 사용되는 객체 생성 불가능 클래스
public class Services {
    public Services() { } //객체 생성방지
    
    //서비스 이름과 서비스 간 대응관계 보관
    
    private static final Map<String, Provider> providers = new ConcurrentHashMap<>();
    public static final String DEFAULT_PROVIDER_NAME = "<dev>";
    
    //제공자 등록 API
    public static void registerDefaultProvider(Provider p) {
        registerProvider(DEFAULT_PROVIDER_NAME, p);
    }
 
    public static void registerProvider(String name, Provider p) {
        providers.put(name, p);
    }
    
    //서비스 접근 API
    public static Service newInstance() {
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
 
    private static Service newInstance(String name) {
        Provider p = providers.get(name);
        if (p == null
            throw new IllegalArgumentException("No provider registered with name : "+ name);
        return p.newService();
    }
    
}
 
cs



정적 팩터리 메서드의 단점


1.  public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다.


2. 정적 팩터리 메서드가 정적 메서드와 확연히 구분되지 않는다는 것.

- API 문서를 보면 생성자는 다른 메서드와 뚜렷이 구별되지만, 정적 팩터리 메서드는 그렇지 않다. 그러니 생성자 대신 정적 팩터리 메서드를 통해 객체를 만들어야 하는 클래스는 사용법을 파악하기가 쉽지 않다. 그래서 클래스나 인터페이스 주석을 통해 정적 팩터리 메서드임을 널리 알리거나, 정적 팩터리 메서드 이름을 지을 때 조심하는 수밖에 없다.


정적 팩터리 메서드의 이름  ex)

- valueOf : 인자로 주어진 값과 같은 값을 갖는 객체를 반환 (형변환 메서드)

- of : valueOf를 더 간단히 쓴 것. (EnumSet)

-  getInstance : 인자에 기술된 객체를 반환하지만, 인자와 같은 값을 갖지 않을 수도 있다.

-  newInstance : getInstance와 같지만 호출할 때마다 다른 객체를 반환한다.

- getType : getInstance와 같지만, 반환될 때 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용. Type은 팩터리 메서드가 반환할 객체의 자로형

- newType : newInstance와 같지만, 반환될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용.  Type은 팩터리 메서드가 반환할 객체의 자료형



요약


정적 팩터리 메서드와  public 생성자는 용도가 서로 다르다.



반응형
profile

거꾸로 바라본 세상

@란지에。

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!