조슈아 블로크 님의 "Effective Java" 책을 정리한 포스팅 입니다.
1. 정적 팩토리 메서드
- 클래스는 생성자와는 별도로 static factory method 제공이 가능함
2. 장점
장점 | 설명 |
이름을 가질 수 있음 | 생성자와 달리 이름으로 의미 전달 가능 |
동일 시그니처 생성자 문제 해결 | 매개변수만 다르고 타입이 같으면 컴파일 에러 발생 → 정적 메서드로 해결 |
인스턴스 캐싱 | 매번 새 객체를 생성하지 않고, 재사용 가능 |
인스턴스 생성 통제 | 싱글턴, 불변 객체, 생성 불가 등 구현 가능 |
반환 타입을 하위 타입으로 지정 가능 | 인터페이스 반환 → 실제 구현은 감춤 |
입력값에 따라 다른 클래스 인스턴스 반환 | 서브클래스를 숨기고, 조건 따라 인스턴스 선택 |
반환 객체 클래스가 미리 없어도 됨 | 정적 팩터리 메서드 작성 시점에 구현체가 없어도 됨 |
서비스 제공자 프레임워크 구현 가능 | 추상화된 API + 구현을 분리 → 유연한 확장 |
인스턴스 생성을 서브클래스에 위임 | 구현체가 인스턴스를 생성하도록 위임 |
예시) 이름을 가짐
더보기
public static Order primeOrder(Product product) {
Order order = new Order();
order.prime = true;
order.product = product;
return order;
}
public static Order urgentOrder(Product product) {
Order order = new Order();
order.urgent = true;
order.product = product;
return order;
}
예시) 동일 시그니처 생성자가 여러 개 필요할 경우
더보기
public class Order {
private Product product;
private boolean prime, urgent;
public Order(Product product, boolean urgent) {
this.product = product;
this.urgent = urgent;
}
public Order(Product product, boolean prime) {
this.product = product;
this.prime = prime;
}
}
예시) 인스턴스 캐싱
더보기
public class Settings {
private boolean useAutoSteering;
private boolean useABS;
private Difficulty difficulty;
private Settings() {}
private static Settings SETTINGS = new Settings();
public static Settings newInstance() return SETTINGS;
}
- 매개 변수에 따라 다른 인스턴스 객체를 리턴할 수 있음 (ex. Boolean.valueOf(boolean))
- 불변 클래스를 다룰 때 유용
- 플라이웨이트 패턴과 유사함
예시) 인스턴스 생성을 철저히 통제할 수 있음
더보기
싱글턴 클래스화
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {} // 외부에서 new 불가
public static Singleton getInstance() {
return INSTANCE;
}
}
불변 객체
public final class Money {
private final int amount;
private Money(int amount) {
this.amount = amount;
}
private static final Map<Integer, Money> CACHE = new ConcurrentHashMap<>();
public static Money of(int amount) {
return CACHE.computeIfAbsent(amount, Money::new);
}
}
인스턴스화 불가
public class MathUtils {
private MathUtils() {
throw new AssertionError("No MathUtils instance allowed");
}
public static int add(int a, int b) {
return a + b;
}
}
열거 타입
public enum AppSettings {
INSTANCE;
public void doSomething() {
System.out.println("Doing something...");
}
}
- 싱글턴을 가장 안전하게 구현할 수 있는 방식
- 직렬화, 리플렉션 회피가 자동으로 보장됨
예시) 입력 매개변수에 따라 매번 다른 클래스의 객체 반환 가능
더보기
전략 패턴
public class EmailSender implements MessageSender {
public void send(String message) {
System.out.println("📧 Email: " + message);
}
}
public class SmsSender implements MessageSender {
public void send(String message) {
System.out.println("📱 SMS: " + message);
}
}
팩토리 메서드
public class MessageSenderFactory {
public static MessageSender getSender(String type) {
return switch (type.toLowerCase()) {
case "email" -> new EmailSender();
case "sms" -> new SmsSender();
default -> throw new IllegalArgumentException("Unknown sender type");
};
}
}
예시) 반환할 객체의 클래스가 존재하지 않아도 됨
더보기
추상화 제공
public static void main(String[] args) {
String driverName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "soyeon";
try {
Class.forName(driverName);
// 서비스 접근 API인 DriverManager.getConnection가 서비스 구현체(서비스 인터페이스)인 Connection 반환
Connection connection = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
- 클라이언트 코드와의 종속성을 낮추고 결합도를 느슨하게 함
- 이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 됨
예) 서비스 제공자 프레임워크
더보기
Logger
public interface Logger {
void log(String message);
}
public class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("[Console] " + message);
}
}
Logger logger = LoggerFactory.getLogger(); // 내부에서 구현체 반환
logger.log("안녕 충밤!");
- 인터페이스를 기준으로, 실제 구현체를 외부에서 플러그인처럼 제공받는 구조
- 클라이언트와 구현이 느슨하게 연결되어 새로운 구현을 쉽게 교체하거나 확장할 수 있음
예) 인스턴스 생성을 서브클래스에게 위임
더보기
abstract class Shape {
protected DrawingAPI drawingAPI;
protected Shape(DrawingAPI drawingAPI) { this.drawingAPI = drawingAPI; }
public abstract void draw();
}
class CircleShape extends Shape {
private double x, y, radius;
public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
public void draw() { drawingAPI.drawCircle(x, y, radius); }
}
- 추상화된 부분과 구현 부분을 서로 다른 클래스 계층구조로 분리
- 서로 영향을 주지 않으면서 독립적으로 확장하도록 하기 위함
- 브릿지 패턴과 유사
3. 단점
단점 | 설명 |
하위 클래스 생성 불가 | 정적 팩터리 메서드만 쓰고 생성자가 private이면 상속이 불가능함 |
프로그래머가 찾기 어려움 | new 키워드 없이 메서드로 객체를 생성하므로 IDE에서 자동으로 찾기 어려움 |
4. 정적 팩토리 메서드 네이밍 규약
메서드 이름 | 의미 | 특징/사용 예 |
from | 하나의 매개변수로 인스턴스 생성 | Duration.from(TemporalAmount amount) |
of | 여러 매개변수로 적절한 인스턴스 생성 | List.of("a", "b"), EnumSet.of(E e1, E... others) |
valueOf | from 또는 of보다 더 명시적이고 세부적 | Boolean.valueOf(true) |
getInstance | 재사용 가능한 인스턴스 반환 (동일 인스턴스 아님) | Calendar.getInstance() |
newInstance | 매번 새로운 객체를 반환함을 보장 | Class.newInstance() |
getType | 팩토리 클래스에서 Type 반환 (재사용 가능) | FileSystem.getFileSystem() |
newType | 팩토리 클래스에서 Type 반환 (항상 새 인스턴스) | DocumentBuilderFactory.newDocumentBuilder() |
'Java' 카테고리의 다른 글
[Effective Java] 2-3. 객체 생성과 파괴: private 생성자나 열거 타입으로 싱글턴임을 보장하라 (0) | 2024.12.27 |
---|---|
[Effective Java] 2-2. 객체 생성과 파괴: 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2024.12.26 |
[JVM 밑바닥까지 파헤치기] 12-3. 자바 메모리 모델과 스레드: 스레드 (1) | 2024.12.26 |
[JVM 밑바닥까지 파헤치기] 12-2. 자바 메모리 모델과 스레드: 자바 메모리 모델 (1) | 2024.12.26 |
[JVM 밑바닥까지 파헤치기] 12-1. 자바 메모리 모델과 스레드: 동시성 (1) | 2024.12.25 |