티스토리 뷰
(EffectiveJava) 규칙 1. 생성자 대신 정적 팩터리 메서드를 고려하라
정적 팩터리 메서드
-
클래스를 통해 객체를 만드는 일반적인 방법은 public 으로 선언된 생성자 (constructor)를 이용하는 방법이다.
-
그러나 모든 프로그래머가 반드시 알고 있어야 하는 방법이 하나 있다.
- 클래스에 public으로 선언된 정적 팩터리 메서드(static factory method)를 추가하는 것
-
Boolean(Java의 기본 타입 중 하나인 boolean을 클래스화 한 것) 클래스에 대한 간단한 예제
//boolean의 값을 Boolean 객체에 대한 참조로 변환 public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
- 클래스를 정의할 때 생성자 대신 정적 팩터리 메서드를 제공할 수 있음
- 단, 정적 팩터리 메서드는 어떠한 디자인 패턴도 아님
정적 팩터리 메서드의 장점
1. 생성자와는 달리 정적 팩터리 메서드에는 이름(name)이 있다.
- 정적 팩터리 메서드는 이름을 잘 짓기만 한다면 사용하기 쉽고 가동성도 높아진다.
- 생성자에 전달되는 인자(parameter)들은 어떤 객체가 생성되는지를 설명하지 못함
- 예) 소수일 가능성이 높은 BigInteger 객체를 생성하는 BigInteger(int, int, Random)은 BigInteger.probablePrime과 같은 이름의 정적 팩터리 메서드로 표현하면 더 이해하기 쉬움
- 시그니처를 갖는 생성자를 여러개 정의할 필요가 있을 때 메서드 이름을 통해서 용도를 파악하기 쉽다.
- 생성자에서는 인자의 순서를 바꾸는 방법으로 이를 해결하는데, 이 방법에는 아주 문제가 많음
- API 사용자는 각각의 생성자 용도를 기억하지 못함
- 즉, API 설명서를 참조하기 않고는 코드가 하는 일을 제대로 파악할 수 없음
- 팩터리 메서드에는 이름이 있기 때문에 작명에 신경을 쓴다면 용도를 파악하기 쉬움
- 생성자에서는 인자의 순서를 바꾸는 방법으로 이를 해결하는데, 이 방법에는 아주 문제가 많음
2. 생성자와는 달리 호출할 떄마다 새로운 객체를 생성할 필요가 없다.
- 변경 불가능 클래스라면 이미 만들어둔 객체를 활용할 수 있다. (객체를 캐시(cache) 해놓고 재사용도 가능)
- 위 소개한 Boolean.valueOf(Boolean) 메서드가 이 기법을 활용한 예
- 동일한 객체가 요청되는 일이 잦고, 객체를 만드는 비용이 클 때 적용하면 성능을 크게 개선 가능
- 경량(Flyweight) 패턴과도 유사함
- 정적 팩터리 메서드를 사용하면 같은 객체를 반복해서 반환할 수 있어 어떤 시점에 어떤 객체가 얼마나 존재할지를 정밀하게 제어할 수 다.
- 이런 기능을 가진 클래스는 개체 통제 클래스(instance-controlled class)라고 부름
- 개체 통제 클래스를 작성하는 이유
- 개체수를 제어하면 싱글턴 패턴을 따르도록 할 수 있음
- 객체 생성이 불가능한 클래스를 만들 수도 있음
- 변경 불가능한 클래스의 경우 두개의 같은 객체가 존재하지 못하도록 할 수도 있음
- a == b 일 때만 a.equals(b)가 참이 되도록 만들 수 있다는 것
- 이렇게 구현된 클래스는 equals(Object) 대신 == 연산자를 사용할 수 있어 성능이 향상됨
- enum이 이 기법을 사용함
3. 생성자와는 달리 반환값 자료형의 하위 자료형 객체를 반환할 수 있다.
- 반환되는 객체의 클래스를 훨씬 유연하게 결정할 수 있다.
- public으로 선언되지 않은 클래스의 객체를 반환하는 API를 만들 수 있음.
- 구현 세부사항을 감출 수 있으므로 간결한 API가 가능
- 인터페이스 기반 프레임워크 구현에 적합하며 인터페이스는 정적 팩터리 메서드의 반환값 자료형으로 이용됨
- 관습상 Type이라는 인터페이스를 반환하는 팩터리 메서드는 Types라는 이름의 객체 생성 불가능 클래스 안에 둠
- 예) Java 컬렉션 프레임워크에는 32개의 컬렉션 인터페이스 구현체가 들어 있는데, 변경이 불가능한 컬렉션과 동기화된 컬렉션 등이다.
- 구현체들 거의 전부는 java.util.Collections라는 객체 생성 불가능 클래스의 정적 팩터리 메서드를 통해 이용하는데 실제 클래스는 public이 아님
- 프레임워크 기법 덕에 간단해진 것은 단순히 API의 구조가 아리나 개념상의 무게(conceptual weight)임
- 사용자들은 별도의 클래스 사용법 문서를 읽지 않고도 구현체를 이용
- 이를 사용한 코드에서는 인터페이스만 보고 작성하기 때문에 일반적으로 바람적인 습관
- JDK 1.5부터 도입된 java.util.EnumSet에는 public으로 선언된 생성자가 없으며, 정적 팩터리 메서드 뿐임
- 메서드들은 enum 상수 개수에 따라 두 개 구현체 가운데 하나를 골라 클래스의 객체를 만들어 반환함
- RegularEnumSet 객체를 반환하는데, 객체는 내부적으로 long 변수 하나만 사용함
- enum 상수들이 64개보다 많은 경우에는 JumboEnumSet 객체를 반환하는데 내부적으로 long 형의 배열을 사용
- 추후에 EnumSet 구현체들의 기능을 변경하거나 추가할 수도 있음
- 클라이언트는 팩터리 메서드가 반환하는 객체의 실제 클래스를 알 수도 없고, 알 필요도 없음
- 단지 EnumSet의 하위 클래스라는 사실만이 중요함
- 메서드들은 enum 상수 개수에 따라 두 개 구현체 가운데 하나를 골라 클래스의 객체를 만들어 반환함
- 다양한 서비스 제공자들이 하나의 서비스를 구성하는 시스템
- 정적 팩터리 메서드가 반환하는 클래스는 정적 팩터리 메서드가 정의된 클래스의 코드가 작성되는 순간에 존재하지 않아도 무방함
- JDBD(Java Database Connectivity API)가 대표적 예이여 근간을 이루는 것이 팩터리 메서드임
- 클라이언트가 세부 구현 내용을 몰라도 실제 구현된 서비스를 이용할 수있도록 함<
- 서비스 제공자 프레임워크의 핵심 컴포넌트
- 서비스 인터페이스
- 서비스 제공자가 구현함
- 제공자 등록 API
- 구현체를 시스템에 등록하여 클라이언트가 쓸 수 있도록 함
- 서비스 접근 API
- 클라이언트에게 실제 서비스 구현체를 제공
- 제공자 선택 기준을 제공할 수도 있고 그렇지 않으면 기본 구현체의 객체를 반환
- 서비스 제공자 인터페이스 (옵션)
- 서비스 제공자가 구현하는 것으로 서비스 구현체의 객체를 생성하기 위한 것
- 없는 경우 구현체는 클래스 이름으로 등록되며 자바의 리플렉션 (reflection) 기능을 통해 객체로 만들어짐
- 예) JDBC
- Connection: 서비스 인터페이스
- DriverManager.registerDriver: 제공자 등록 API
- DriverManager.getConnection: 서비스 접근 API
- Driver: 서비스 제공자 인테페이스
- 서비스 제공자 프레임워크 패턴에는 다양한 변종들이 있음
- 예) 서비스 접근 API에 어댑터 패턴을 적용하면 제공자에게 요구되는 것보다 풍부한 기능을 제공하는 서비스 인터페이스 객체를 제공하는 서비스 인터페이스 객체를 반환할 수 있음
//서비스 제공자 인터페이스의 대략적인 모습 //서비스 인터페이스 public interface Service { ... //서비스의 고유한 메서드들이 이 자리에 온다 } //서비스 제공자 인터페이스 public interface Provider { Service newService(); } //서비스 등록과 접근에 사용되는 객체 생성 불가능 클래스 public class Services { private Services() { } //객체 생성 방지 //서비스 이름과 서비스간 대응관계 보관 private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>(); public static final String DEFAULT_PROVIDER_NAME = "<def>"; //제공자 등록 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); } public 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(); } }
- 예) 서비스 접근 API에 어댑터 패턴을 적용하면 제공자에게 요구되는 것보다 풍부한 기능을 제공하는 서비스 인터페이스 객체를 제공하는 서비스 인터페이스 객체를 반환할 수 있음
- 서비스 인터페이스
- public으로 선언되지 않은 클래스의 객체를 반환하는 API를 만들 수 있음.
4. 형인자 자료형(parameterized type) 객체를 만들 때 편하다.
- 아래와 같은 클래스의 생성자를 호출할 때는, 문맥상 형인자가 명백하더라도 반드시 인자를 형인자로 전달해야 한다.
- 보통은 이렇게 형인자를 두 번 사용하게 됨
- Java 1.6 까지는 생성자 호출 시에 자료형 유추를 지원하지 않음
Map<String, List<String>> m = new HashMap<String, List<String>>();
- Java 1.6 까지는 생성자 호출 시에 자료형 유추를 지원하지 않음
- 자료형 명세를 중복하면, 형인자가 늘어남에 따라 기록 복잡한 코드가 만들어짐
- 보통은 이렇게 형인자를 두 번 사용하게 됨
- 정적 팩터리 메서드를 사용하면 컴파일러가 형인자를 스스로 알아내도록 할 수 있다.
- 이런 기법은 자료형 유추(type interface)라고 함
- 예) HashMap 클래스가 아래의 제네릭 정적 팩터리 메서드를 제공한다고 가정 해보면
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); }
- 위 메소드가 있으면 앞서 살펴본 선언물을 좀 더 간결하게 작성할 수 있음
다행히도 자바 1.7부터는 생성자를 호출 할때도 자료형 유추를 사용할 수 있음Map<String, List<String>> m = new HashMap<>();
- 따라서 표준 컬렉션 메서드에 정적 메[서드 팩터리 메서드를 추가할 필요가 없어짐
정적 팩터리 메서드의 단점
1. public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다.
- 예를 들어, 자바의 컬렉션(Collections) 프레임워크에 포함된 기본 구현 클래스들의 하위 클래스는 만들수 없다.
- 논쟁의 소지가 있긴 하지만 그래서 더 좋다는 사람도 있음
- 그건 계승(inheritance) 대신 구성(composition) 기법을 쓰도록 장려한다는 이유 (규칙 16)
2. 정적 팩터리 메서드가 다른 정적 메서드와 확연히 구분되지 않는다.
- API 문서를 보면 생성자는 다른 메서드와 뚜렷이 구분되지만, 정적 팩터리 메서드는 그렇지 않다.
- 그렇기에 생성자 대신 정적 팩터리 메서드를 통해 객체를 만들어야 하는 클래스는 파악하기가 쉽지 않음
- 따라서, 클래스나 인터페이스 주석을 통해 알리거나 정적 팩터리 메서드 이름을 지을 때 조심하는 수 밖에 없다.
- 보통 쓰이는 정적 팩터리 메서드의 이름
- valueOf(): 인자로 주어진 값과 같은 값을 갖는 객체를 반환한다는 뜻.
- of(): valueOf를 더 간단하게 쓴 것. EnumSet덕분에(규칙32) 인기를 모은 이름.
- getInstance(): 인자에 기술된 객체를 반환하지만, 인자와 같은 값은 값을 갖지 않는 수도 있음. 싱글턴 패턴을 따를 경우, 이 메서드는 인자 없이 항상 같은 객체를 반환함.
- newInstance(): getInstance와 같지만, 호출할 때마다 다른 객체를 반환.
- getType(): getInstance와 같지만, 반환될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용. Type은 팩터리 메서드가 반활할 객체의 자료형임.
- newType(): newInstance와 같지만, 반활될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용. Type은 팩터리 메서드가 반활할 객체의 자료형임.
- 보통 쓰이는 정적 팩터리 메서드의 이름
결론
-
정적 팩터리 메서드와 public 생성자는 용도가 서로 다르면, 그 차이와 장단점을 이해하는 것이 중요하다.
-
정적 팩터리 메서드가 효과적인 경우가 많으니, 정적 팩터리 메서드를 고려해 보지도 않고 무조건 public 생성자를 만드는 것은 삼가기 바람.
끝으로
이 글이 도움이 되었다면, Google 광고 한번씩 클릭 부탁 드립니다. 🙏🙏🙏
광고 클릭은 많은 힘이 됩니다!
반응형
'프로그래밍 > EffectiveJava' 카테고리의 다른 글
(이펙티브 자바) 규칙20. 추상 클래스 보다는 인터페이스를 우선하라 (0) | 2020.02.18 |
---|---|
(이펙티브 자바) 규칙18. 상속보다는 컴포지션을 사용하라 (0) | 2020.02.13 |
(이펙티브 자바) 규칙6. 불필요한 객체 생성을 피하라 (0) | 2020.02.10 |
(이펙티브 자바) 규칙4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2020.02.06 |
(이펙티브 자바) 규칙3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2020.02.06 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 자전거
- 일본 배낭여행
- effective java
- 배낭 여행
- 인텔리제이
- JavaFX Table View
- 자바
- 일본 여행
- 텐트
- JavaFX 테이블뷰
- JavaFX
- 이펙티브
- 이펙티브자바
- 방통대 과제물
- Java UI
- windows
- springboot
- 일본여행
- JavaFX Window Close
- intelij
- java
- 배낭여행
- 일본 자전거 여행
- 스프링부트
- effectivejava
- JavaFX 종료
- TableView
- git
- 자전거 여행
- 이펙티브 자바
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함