Effective Java 3/E판을 읽고 정리한 기록입니다.
정적 팩터리 메소드란?
미리 정적 팩토리 메서드가 무엇인지에 대해 정리를 해놨습니다.
미리 읽고 책의 정리를 보면 좋을 것 같아, URL 공유드립니다!
정적 팩토리 메서드(Static Factory Method)가 생성자보다 좋은 점
1. 이름을 가질 수 있다.
생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못하지만,
정적 팩터리는 이름만 잘 지으면 객체 특성을 쉽게 묘사할 수 있습니다.
하나의 시그니처로는, 생성자를 하나만 만들 수 있지만,
정적 팩터리 메서드를 사용하면, 여러 개의 메소드를 만들 수 있다는 장점이 있습니다.
메소드나 생성자 등의 멤버에 대한 고유한 식별자를 시그니처(signature)라 하는데,
이 식별자는 생성자의 이름과 매개변수의 타입 및 개수로 구성됩니다.
따라서 시그니처는 메소드의 이름과 매개변수의 형식을 나타내는 말이며,
시그니처를 통해 메소드를 고유하게 식별할 수 있습니다.
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
정적 팩터리 메소드는, 인스턴스를 캐싱하여 이전에 생성된 인스턴스를 재사용할 수 있습니다.
따라서 동일한 인스턴스를 반복해 반환 가능합니다.
정적 팩토리 메서드를 사용하여 인스턴스를 관리 시, 싱글톤 패턴을 쉽게 구현할 수 있습니다.
즉, 하나의 클래스에 대해 오직 하나의 인스턴스만 존재하도록 보장할 수 있습니다.
정적 팩토리 메소드는 생성된 인스턴스 유형을 유연하게 선택할 수 있습니다.
예를 들어, 여러 하위 클래스의 인스턴스 중에서 적절한 인스턴스를 선택하여 반환할 수 있습니다.
이는 팩토리 메소드 패턴(Factory Method Pattern)을 구현하는 데 유용합니다.
정적 팩터리 메소드는 인스턴스 생성 로직을 클래스 내부에 캡슐화하여 외부에서 직접적인 인스턴스 생성을 제어할 수 있습니다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
정적 팩터리 메서드가 인스턴스 생성과 관련된 유연성을 제공하는데 도움이 되는 중요한 특징 중 하나입니다.
- 인터페이스 기반 프로그래밍(Interface-based programming)
- 정적 팩터리 메서드를 사용 시, 실제 구현 클래스가 아닌, 인터페이스 타입을 반환할 수 있습니다.
- 클라이언트 코드는 반환된 인터페이스를 통해서만 객체에 접근하므로, 구현 클래스에 대한 의존성이 줄어듭니다.
- 하위 타입 다형성(Subtype polymorphism)
- 정적 팩터리 메서드는 반환 타입의 하위 타입 객체를 반환할 수 있습니다.
- 이것은 클라이언트가 호출한 팩터리 메서드의 리턴값을 통해 여러가지 다른 하위 클래스의 인스턴스를 얻을 수 있음을 의미합니다.
- 유연한 객체 생성 방법(Flexible object creation)
- 정적 팩터리 메서드는 객체 생성 방법을 자유롭게 선택할 수 있습니다.
- 이는 팩터리 메서드 내부에서 생성된 객체의 유형을 동적으로 결정할 수 있음을 의미합니다.
- 캐싱 및 재사용(Caching and reusing)
- 정적 팩터리 메서드는 내부적으로 인스턴스를 캐싱하거나 재사용 가능합니다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
정적 팩터리 메소드가, 다양한 매개변수에 따라 다른 구현 클래스의 인스턴스를 반환할 수 있다는 의미입니다.
이것은 정적 팩토리 메서드가 인스턴스 생성을 담당하는 팩토리 역할을 하며,
매개변수를 기반으로 어떤 구현 클래스의 인스턴스를 생성할지 결정할 수 있다는 것을 의미합니다.
아래는 예시, 정적 팩토리 메소드 코드입니다
public static PaymentProcessor createPaymentProcessor(String paymentMethod) {
if ("creditCard".equals(paymentMethod)) {
return new CreditCardPaymentProcessor();
} else if ("paypal".equals(paymentMethod)) {
return new PaypalPaymentProcessor();
} else {
throw new IllegalArgumentException("Unsupported payment method: " + paymentMethod);
}
}
위의 코드에서 createPaymentProcessor 메서드는, 입력 매개변수인 paymentMethod를 따라서 매번 다른 클래스의 객체를 반환합니다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
이러한 유연함은 서비스 제공자 프레임워크(service provideer framework)를 만드는 근간이 된다는데,
대표적인 서비스 제공자 프레임워크로는 JDBC(Javaz Database Connectivity)가 있습니다.
서비스 제공자 프레임워크(Service Provider Framework)란?
인터페이스와 이를 구현하는 서비스 제공자의 분리를 통해 애플리케이션의 유연성과 확장성을 향상시키는 디자인 패턴 중 하나.
구성요소
1. 서비스 인터페이스(Service Interface)
서비스 제공자와 서비스 사용자 간의 통신을 위한 인터페이스 정의.
서비스의 기능과 동작을 명시적으로 정의하고 클라이언트 코드가 해당 서비스와 상호 작용할 수 있도록 함.
2. 서비스 제공자(Service Provider)
서비스 인터페이스를 구현하는 구체적인 클래스 또는 모듈.
팩터리 메소드나 다른 방법을 통해 서비스 인스턴스를 생성하고 반환
클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리해줌
3. 서비스 등록 메커니즘(Service Registration Mechanism)
서비스 제공자가 제공하는 서비스를 애플리케이션에 등록하는 메커니즘.
일반적으로 컨테이너나 레포지토리를 통해 이루어진다.
4. 서비스 엑세스 API(Service Access API)
서비스 사용자가 서비스를 검색하고 호출할 수 있는 API 제공.
서비스 인터페이스와 밀접하게 관련되어 있으며, 서비스 제공자의 실제 구현을 숨기고 사용자에게 인터페이스를 통해 서비스에 접근할 수 있는 방법 제공.
JDBC(Java Database Connectivity)가 서비스 제공자 프레임워크의 대표적인 예인 이유?
- 인터페이스와 구현의 분리
- JDBC는 connection, Statement, ResultSet 등과 같은 인터페이스를 정의하여 데이터베이스에 대한 상호 작용을 추상화합니다.
- 이러한 인터페이스들은 서비스 제공자 패턴을 따르며, 각 데이터 베이스 공급 업체는 이러한 인터페이스를 구현해 자신의 데이터베이스에 액세스 하는 방법을 제공합니다.
- 서비스 제공자 등록
- JDBC 드라이버는 자바에서 특정 데이터베이스 제품과 통신할 수 있도록 하는 드라이버 클래스입니다.
- JDBC는 이러한 드라이버 클래스를 동적으로 로딩하고, 등록하는 방법을 제공합니다.
- 따라서 애플리케이션은 실행 시점에 사용할 데이터베이스 제품에 따라 적절한 드라이버 클래스를 선택할 수 있습니다.
- 유연한 확장성
- JDBC는 데이터베이스 제품이나, 버전이 변경되어도 코드의 수정 없이 새로운 드라이버 클래스를 추가하고 사용할 수 있습니다.
- 이는 서비스 제공자 패턴을 통해 가능한 것으로, JDBC 인터페이스와 각 드라이버 클래스 사이의 분리를 통해 실현됩니다.
- 다양한 데이터베이스 지원
- JDBC는 다양한 데이터베이스 제품을 지원합니다.
- 표준화된 API
- JDBC는 자바 표준 API로서 공식적으로 정의되어 있습니다. 이는 자바 개발 환경에서 일관된 방식으로 데이터베이스에 접속하고 SQL 쿼리를 실행할 수 있도록 합니다.
이러한 이유로, JDBC는 서비스 제공자 패턴의 핵심 개념을 따르며, 데이터베이스와의 상호작용을 추상화하고 분리함으로써 자바 애플리케이션에서 데이터베이스 액세스를 유연하고 확장 가능하게 만듭니다.
정적 팩터리 메소드의 단점
1. 상속을 하려면 public이나 protected 생성자가 필요하니, 정적 팩터리 메소드만 제공하면 하위 클래스를 만들 수 없다.
밑의 코드는 예시코드입니다.
public class ParentClass {
private String name;
// private 생성자
private ParentClass(String name) {
this.name = name;
}
// 정적 팩터리 메소드
public static ParentClass createInstance(String name) {
return new ParentClass(name);
}
public String getName() {
return name;
}
}
다음과 같은 부모 클래스가 있을 때, 이 클래스는 private 생성자를 가지고 있고, 대신 createInstance()라는 정적 팩터리 메서드를 통해 객체를 생성할 수 있습니다.
하지만 이 클래스를 밑의 코드처럼 상속해 하위 클래스를 만든다면,
public class ChildClass extends ParentClass {
// 하위 클래스의 생성자
public ChildClass(String name) {
super(name);
}
}
ParentClass의 생성자가 private이기 때문에 하위 클래스에서는 이를 호출할 수 없고, 컴파일 오류가 발생합니다.
이를 해결하기 위해, ParentClass의 생성자 접근 제어자를 public 또는 protected로 변경해야 하는데,
이렇게 수정한다면 외부에서도 ParentClass의 생성자에 접근할 수 있게 되어 클래스의 불변성이 깨집니다.
따라서, 정적 팩터리 메소드만 제공 시, 하위 클래스를 만들 수 없다는 단점이 있습니다.
상속을 고려한다면 생성자의 접근 제어자를 고려해 하위 클래스에서 생성자를 호출할 수 있도록 해야 합니다.
2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
- 명명 규칙의 일관성 부족
- 정적 팩토리 메서드의 명명 규칙은 일반적으로 valueOf(), of(), newInstance() 등과 같은 표준적인 패턴을 따르지만, 모든 클래스가 이러한 규칙을 따르지 않을 수 있습니다.
- API 문서의 미비
- 일부 API 문서에서는 정적 팩토리 메서드를 명시적으로 문서화하지 않을 수 있습니다.
산군의 CTO 준혁님께서 선물해 주신 Effective java 3/E판의 Item 1을 공부해 봤습니다!! :)
아직 부족한 부분이 많아, 제가 잘못 공부한 부분이 있다면 언제든지 댓글로 알려주시면 감사하겠습니다:>
언제나 즐거운 개발공부 되세요 :>!!
'Effective Java' 카테고리의 다른 글
Effective Java | Item 6. 불필요한 객체 생성을 피해라 (0) | 2024.05.27 |
---|---|
Effective Java | Item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2024.04.23 |
Effective Java | Item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2024.04.23 |
Effective Java | Item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라. (0) | 2024.03.28 |
Effective Java | Item 2. 생성자에 매개변수가 많다면 빌더를 고려하라. (0) | 2024.03.27 |