✍️ 학습 목표
- 객체 생성하기
- 정적 팩토리 메서드의 장점
- 정적 팩토리 메서드의 단점
📌 객체 생성하기
객체를 생성하는 가장 흔한 방법은 public 생성자를 사용하는 것이다.
정적 팩토리 메서드를 사용해서도 객체를 생성할 수 있다. 차이는 다음과 같다.
🧐 public 생성자
class Pokemon {
private int attack, defense, hp;
public Pokemon(int attack, int defense, int hp) {
this.attack = attack;
this.defense = defense;
this.hp = hp;
}
}
public class Item1 {
public static void main(String[] args) {
Pokemon 피카츄 = new Pokemon(5,5,5);
}
}
🧐 정적 팩토리 메서드
쉽게 이해되지 않는 정적 팩토리 메서드란 무엇일까??
정적 메서드(static method)를 사용해서 객체를 만드는(=factory) 것을 의미한다.
class Pokemon {
private int attack, defense, hp;
private Pokemon(int attack, int defense, int hp) {
this.attack = attack;
this.defense = defense;
this.hp = hp;
}
public static Pokemon newPokemon(int attack, int defense, int hp) {
return new Pokemon(attack, defense, hp);
}
}
public class Item1 {
public static void main(String[] args) {
Pokemon 피카츄 = Pokemon.newPokemon(5, 5, 5);
}
}
📌 정적 팩토리 메서드의 장점
1️⃣ 이름을 가질 수 있다.
생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다.
반면에 정적 팩토리 메서드는 이름만 잘 지으면 반환될 객체의 특성을 잘 묘사할 수 있다.
// public 생성자
Pokemon 피카츄 = new Pokemon(5, 5, 5);
Pokemon 꼬부기 = new Pokemon(6, 7, 4);
// 정적 팩토리 메서드
Pokemon 피카츄 = Pokemon.newPikachu();
Pokemon 꼬부기 = Pokemon.newSquirtle();
생성자를 여러 개 구현해서 이 제한을 피할 수도 있지만 엉뚱한 생성자를 호출할 수 있다는 점과 코드를 읽는 사람이 설명 없이는 이해할 수 없다는 점에서 추천하지 않는다.
2️⃣ 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
public final class Boolean implements java.io.Serializable,
Comparable<Boolean>, Constable {
// 미리 선언되어있는 static 필드
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
...
public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
...
}
Boolean의 valueof 메서드의 내부 구조를 보면 인스턴스를 미리 캐싱해두고 리턴한다.
이렇게 새로 생성한 인스턴스를 캐싱하여 재활용함으로 불필요한 객체 생성을 피할 수 있다.
특히, 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 올려줄 수 있다.
이런 방식의 클래스는 인스턴스의 생명주기를 제어할 수 있어 인스턴스 통제 클래스라고 부른다.
생명주기를 제어함으로 싱글톤 클래스로, 또는 인스턴스화 불가 클래스로도 만들 수 있다.
인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다.
class Pokemon {
private static Pokemon pokemon;
private int attack, defense, hp;
private Pokemon(int attack, int defense, int hp) {
this.attack = attack;
this.defense = defense;
this.hp = hp;
}
public static Pokemon newPokemon() {
if (pokemon == null) {
pokemon = new Pokemon(5, 5, 5);
}
return pokemon;
}
}
3️⃣ 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
ArrayList의 asList() 메서드를 사용해봤다면 보다 쉽게 이해할 수 있다.
입력받은 배열을 ArrayList로 반환하는데 우리는 내부 구현이 어떻게 되어있는지 알 필요가 없다.
정적 팩토리 메서드가 가지는 이 유연성으로 API를 보다 사용하기 쉽게 만들 수 있다.
아래 코드와 같이 정적 팩토리 메서드를 활용하면 Grade 쪽만 읽어보고 of() 메서드만 쓰면 바로 사용할 수 있다.
class Grade {
public static Grade of(int score) {
if (score == 100) {
return new Perfect();
} else if (score > 80) {
return new Good();
} else {
return new Soso();
}
}
}
class Perfect extends Grade {};
class Good extends Grade {};
class Soso extends Grade {};
4️⃣ 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
내부 생성자에 로직을 추가하면 또 다른 클래스의 객체를 반환할 수도 있다.
public enum Status {
Good(100, 75),
SoSo(74, 40),
Bad(39, 0);
private final int maximum;
private final int minimum;
private Status(int maximum, int minimum) {
this.maximum = maximum;
this.minimum = minimum;
}
public static Status of(int point) {
return Arrays.stream(values())
.filter(decideStatus(point))
.findAny()
.orElseThrow(() -> new NoSuchElementException("일치하는 상태가 없습니다!"));
}
private static Predicate<Status> decideStatus(int point) {
return element -> element.maximum >= point && element.minimum <= point;
}
}
5️⃣ 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
나중에 만들 클래스가 반환될 객체를 상속를 받는다면 문제가 없다는 의미이다.
다음 코드를 보면, 포켓몬이 어떤 구현도 되어있지 않지만 정적 팩토리 메서드를 작성하는데 문제는 없다.
포켓몬의 구현체를 구현해주기만 한다면 오히려 더 유연성 있는 코드를 작성할 수 있다.
interface 포켓몬 { }
class 포켓몬친구들 {
public static List<포켓몬> getInstance() {
return new ArrayList<>();
}
}
📌 정적 팩토리 메서드의 단점
1️⃣ 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
상속을 할 때 public or protected 생성자가 필요해서 하위 클래스를 만들 수 없다.
그러나 이러한 제약은 상속보다는 컴포지션을 유도하고,
불변 타입을 만들기 위해 이 제약을 지켜야 한다는 점이 오히려 장점이 될 수도 있다.
✍️ 컴포지션 : 기존 클래스의 인스턴스를 참조하는 방식
2️⃣ 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
생성자처럼 API 설명에 명확히 드러나지 않는다.
따라서 API 문서를 잘 써놓고 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해야 한다.
- from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 리턴
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 리턴
- valueof : from과 of의 더 자세한 버전
- instance 혹은 getInstance : 매개변수로 명시한 인스턴스를 리턴하지만, 같은 인스턴스 보장 ❌
- create 혹은 newInstance : instance 혹은 getInstance와 같지만, 새로운 인스턴스를 생성해 리턴
- getType : getInstance와 같으나, 생성한 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용
- newType : newInstance와 같으나, 생성한 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용
- type : getType과 newTpye의 간결한 버전
'개인 공부 > Java (이펙티브 자바)' 카테고리의 다른 글
[이펙티브 자바] 아이템 6 : 불필요한 객체 생성을 피하라 (0) | 2023.01.15 |
---|---|
[이펙티브 자바] 아이템 5 : 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.01.15 |
[이펙티브 자바] 아이템 4 : 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2023.01.02 |
[이펙티브 자바] 아이템 3 : private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2022.12.21 |
[이펙티브 자바] 아이템 2 : 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2022.12.21 |
댓글