Effective Java - 생성자 대신 정적 팩토리 메서드를 고려하라

2025. 2. 2. 21:59JAVA/Effective Java

첫 번째, 이름을 가질 수 있다.

MemberV1.java

 

MemberV1 클래스는 name, email, password 를 필드로 가진다.

만약 name, email 만을 입력받아 객체를 생성하는 경우, email 과 password 만을 입력받아 객체를 생성하는 경우 기존 생성자 방식으로는 메소드 시그니처가 중복되기 때문에 구현이 불가능하다. (MemberV1(String, String))

 

정적 팩토리 메서드를 사용하면 다음과 같이 구현할 수 있다.

 

MemberV2.java

 

기본 생성자는 클라이언트에서 잘못 사용되지 않도록 public -> private 으로 변경하여 MemberV2 내부에서만 사용하도록 수정한다. createMemberV2WithNameAndEmail 과 createMemberV2WithEmailAndPassword 라는 정적 팩토리 메서드를 구현하여 클라이언트로 하여금 혼동 없이 MemberV2 객체를 생성할 수 있도록 제공한다.

 

두 번째, 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

인스턴스를 호출하는데에 비용이 많이 드는 경우가 존재한다. (DB연결, 쓰레드풀 등) 이런 경우에는 여러가지 방법이 있는데, 일반적으로 싱글턴 패턴, 인스턴스 캐싱, 사전 인스턴스 생성 등의 방법이 존재한다. 여기서는 DB 연결 예제를 통해 알아본다.

 

1. 싱글턴 패턴

DatabaseConnectoinV1

 

싱글턴 패턴과 정적 팩토리 메서드 방식을 사용하면 다음과 같이 구현된다. 기본 생성자는 private 으로 선언하여 클라이언트로부터의 호출을 막고, 컴파일 시에 DatabaseConnectionV1 을 통해 인스턴스가 생성된다. 클라이언트는 오직 getInstance 라는 정적 팩토리 메서드를 통해서만 해당 객체의 인스턴스를 얻을 수 있다. 

 

DatabaseConnectionMainV1

 

2. 인스턴스 캐싱

DatabaseConnectionV2

 

인스턴스 캐싱 방법은 인스턴스를 담아둘 수 있도록 Map<> 과 같은 Key-Value 구조를 사용하여 구현할 수 있다. 마찬가지로 기본 생성자는 private 으로 선언하여 클라이언트 호출을 막는다. 클라이언트는 getConnection을 사용하여 커넥션 획득을 시도하고, 커넥션이 존재하지 않으면 신규로 생성하고, 이미 존재한다면 CONNECTION_POOL 에서 해당 커넥션을 찾아 반환한다.

 

DatabaseConnectionMainV2

 

3. 사전 인스턴스 생성

DatabaseConnectionV3

 

사전 인스턴스 생성 방식은 컴파일 시점에 미리 정의해둔 인스턴스만을 생성하고, 클라이언트는 해당 인스턴스만을 호출하여 사용할 수 있는 방식이다. 컴파일 시점에 PRODUCTION 과 DEVELOPMENT 를 생성하고, 클라이언트는 getEnvironmentConnection 을 사용하여 커넥션을 얻는다.

 

세 번째, 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

정적 팩토리 메소드에는 상위 타입 객체를 반환하도록 작성하고, 클라이언트에서는 실질적으로 하위 타입 객체를 반환하여 사용할 수 있다.

 

Animal, Dog, Cat

 

Animal 인터페이스를 선언하고, Animal 을 상속받는 Dog, Cat 을 구현했다.

 

AnimalFactory.java

 

AnimalFactory 에서는 정적 팩토리 메서드 createAnimal 을 구현하여 매개변수 type 에 따라 각기 다른 구현체(Dog, Cat)를 리턴하도록 구현했다.

 

AnimalMain.java

 

클라이언트에서는 Dog 와 Cat 이 아닌 Animal 타입으로 인터페이스를 반환하고, 해당 인터페이스를 상속받은 실질적인 구현체인 Dog 와 Cat 의 오버라이드된 메소드를 사용할 수 있다.

 

이펙티브 자바에서 설명하는 자바 컬렉션 프레임워크에 대해서 다음과 같이 설명한다. 해당 프레임워크는 총 45개의 유틸리티 구현체를 제공하며, 이는 단 하나의 인스턴스화 불가 클래스인 java.util.Collections 에서 제공된다.

Collections.unmodifiableList()

 

소스를 살펴보면 unmodifiableList 라는 정적 팩토리 메소드는 List 인터페이스를 상속받은 UnmodifiableList 클래스를 반환한다. 이 클래스는 공개되지 않기 때문에 클라이언트는 해당 클래스가 List 인터페이스의 메소드대로 동작할 것을 쉽게 예측할 수 있다.

 

네 번째, 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

키의 길이에 따라 AES 암호화, RSA 암호화를 할 수 있는 각기 다른 클래스를 반환하는 클래스를 구현한다.

 

Encryptor, AESEncryptor, RSAEncryptor

 

Encryptor 인터페이스를 선언하고, 해당 인터페이스를 상속받는 AESEncryptor, RSAEncryptor 를 구현한다.

EncryptorFactory.java

 

EncryptorFactory 에서는 keySize 라는 매개변수 크기에 따라 256 미만이면 AESEncryptor 를 반환하고, 그렇지 않으면 RSAEncryptor 를 반환한다.

 

EncryptorMain.java

 

클라이언트에서는 입력 매개변수 keySize에 따라 다른 클래스의 객체를 반환하여 사용할 수 있다.