언어/Effective Java

이팩티브 자바 Item 1 - 생성자 대신 정적 팩터리 메서드를 고려하라

now0204 2024. 5. 27. 11:06

 

인스턴스를 생성하는 방법은 (1) public 생성자 (2) 정적 팩터리 메서드 사용이 있다. 

 

이때, 정적 팩터리 메서드 메서드를 사용하면 다음과 같은 장점이 있다.


1. 생성 목적에 대한 이름 표현이 가능하다. (객체 생성의 가독성이 높아진다)

 

  • 생성자는 클래스명으로만 구현할 수 있지만, 정적팩터리 메서드는 자신의 이름을 가질 수 있다.
  • new 키워드를 통해 생성자를 생성하면, 해당 생성자의 인수 순서와 내부구조를 알고 있어야 목적에 맞게 객체를 생성할 수 있다는 번거로움 존재
  • 정적 팩터리 메서드 구현 시 자주 사용되는 이름이 있다! 
    • from: 매개변수를 받아서 해당 타입의 인스턴스 반환
    • of: 여러 매개변수 받아서 적합한 인스턴스 반환
    • valueOf : from, of보다 자세한 버전 
    • instance, getinstance: 매개변수 인스턴스를 반환하지만 보장 x
    • create,newInstance: 매번 새로운 인스턴스 생성해 반환
    • getType: 반환 타입과 팩터리메서드 클래스가 다름, Type은 반환 타입 명시 
      • FileStore fs = Files.getFileStore(path)
    • type: getType, newType 간결한 버전 
      • Collections.list()

 

// 브랜드명과 자동차 색을 정의한다고하면
// 브랜드명은 반드시 입력받아야하지만, 자동차는 검정(선택적)
// 선택과 필수속성을 위해 생성자를 두개 만들어야한다.
class Car {
    private String brand;
    private String color = "black";

    public Car(String brand, String color) {
        this.brand = brand;
        this.color = color;
    }

    public Car(String brand) {
        this.brand = brand;
    }
}
  • 매개변수의 유형과 개수를 제안할 뿐 어떤 역할을 하는지 편의성 제공이 없다. 즉, 반환될 객체의 특성을 제대로 표현하기 어렵다.
  • 따라서 정적 메서드를 통해 적절한 메서드 네이밍을 해주면 반환될 객체의 특성을 한번에 유추할 수 있게 된다.
class Car {
    private String brand;
    private String color;

    // private 생성자
    private Car(String brand, String color) {
        this.brand = brand;
        this.color = color;
    }

    // 정적 팩토리 메서드 (매개변수 하나는 from 네이밍)
    public static Car brandBlackFrom(String brand) {
        return new Car(brand, "black");
    }

    // 정적 팩토리 메서드 (매개변수 여러개는 of 네이밍)
    public static Car brandColorOf(String brand, String color) {
        return new Car(brand, color);
    }
}

 


 

2.인스턴스에 대해 통제 및 관리가 가능하다.

 

  • 메서드를 한단계 거쳐 간접적으로 객체를 생성해서 기본적으로 전반적인 객체 생성 및 통제를 할 수 있다.
  • 필요에 따라 항상 새로운 객체를 생성해서 반환할 수도 있고, 아니면 객체 하나만 만들어서 공유
  • 대표적으로 Singleton 디자인 패턴을 예로 들 수 있는데, getInstance()라는 정적 팩토리 메서드를 사용해 오로지 하나의 객체만 반환하도록 하여 객체 재사용을 통해 메모리 절약
  • 인스턴스에 대한 캐싱
class Singleton {
    private static Singleton instance;

    private Singleton() {}

    // 정적 팩토리 메서드
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 인스턴스 통제는 인스턴스가 하나 뿐임을 보장하고, 이는 Flyweight 디자인 패턴의 근간이 된다.
class Day{
	private String day;
    
    public Day(String day) {this.day=day;}
    
    public String getDay() {return day;}

}

class DayFactory{

	//Day 객체를 저장하는 캐싱 저장소 
    private static final Map<String, Day> cache = new HashMap<>();
    
    static{
    	cache.put("Monday", new Day("Monday")); 
        cache.put("Tuesday", new Day("Tuesday")); 
        cache.put("Wednesday", new Day("Wednesday")); 
    }

	public static Day from(String day){
        
        if(cache.containsKey(day)){
        	return cache.get(day);
        }else{
        	
            Day d = new Day(day);
            cache.put(day,d);
        	return d;
        }
    }

}
void boolean1 = Boolean.valueOf(true);

// Boolean을 반환하지만, 매번 객체를 생성하지 않고, 한번 생성되면 값을 변경할 수 없음

3. 하위 자료형 객체를 반환할 수 있다.

 

  • 다형성의 특징을 응용한 정적 팩터리 메서드의 특징이다. 메서드 호출을 통해 얻은 객체의 인스턴스를 자유롭게 선택할 수 있는 유연성을 갖는다. 
interface SmarPhone {}

class Galaxy implements SmarPhone {}
class IPhone implements SmarPhone {}
class Huawei implements SmarPhone {}

class SmartPhones {
    public static SmarPhone getSamsungPhone() {
        return new Galaxy();
    }

    public static SmarPhone getApplePhone() {
        return new IPhone();
    }

    public static SmarPhone getChinesePhone() {
        return new Huawei();
    }
}

//인터페이스에서 정적 메서드 
interface SmarPhone {
    public static SmarPhone getSamsungPhone() {
        return new Galaxy();
    }

    public static SmarPhone getApplePhone() {
        return new IPhone();
    }

    public static SmarPhone getChinesePhone() {
        return new Huawei();
    }
}
  • 이와 같은 방법을 자바에서 java.util.Collections클래스 등에서 활용한다. 
  • java 8 부터는 인터페이스가 정적 메서드를 가질수 있게 되어 인터페이스에서 정적 팩토리 메서드를 선언할 수도 있다. 

 


 

4. 인자에 따라 다른 객체를 반환하도록 분기할 수 있다. 

 

  • 메서드이기 때문에 매개변수를 받아서 분기문을 통해 여러 자식 타입의 인스턴스를 반환하도록 응용할 수 있다.
interface SmartPhone{
	public static SmartPhone getPhone(int price){
    	if(price > 1000000) return new IPhone();
        if(price > 5000) return new Galaxy();
        return new Huawei();
    }

}

 


5. 객체 생성을 캡슐화 할 수 있다. 

 

  • 생성자 사용 시 외부에 내부 구현을 드러내야 하는데, 정적 팩터리 메서드는 외부로 부터 숨길 수 있어 캡슐화 및 정보 은닉을 할 수 있다.
  • 하위 객체로 반환할 수 있음의 확장이다.
  • 특정 값을 받아서, 분기해서 다른 하위 객체를 반환할 때 내부 구현은 몰라도 된다!
interface Grade {
    String toText();
}

class A implements Grade {
    @Override
    public String toText() {return "A";}
}

class B implements Grade {
    @Override
    public String toText() {return "B";}
}

class C implements Grade {
    @Override
    public String toText() {return "C";}
}

class D implements Grade {
    @Override
    public String toText() {return "D";}
}

class F implements Grade {
    @Override
    public String toText() {return "F";}
}

class GradeCalculator {
	// 정적 팩토리 메서드
    public static Grade of(int score) {
        if (score >= 90) {
            return new A();
        } else if (score >= 80) {
            return new B();
        } else if (score >= 70) {
            return new C();
        } else if (score >= 60) {
            return new D();
        } else {
            return new F();
        }
    }
}

6. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않다도 된다.

 

  • 3번활용이다. 인터페이스로 반환한다고 해두고, 추후에 갈아끼울 수 있다. 

 

단점 

 

1. 상속을 하려면, public이나 protected 생성자가 필요하기 때문에 정적 펙토리 메서드만 제공하면 하위 클래스 만들 수 없다.

2. 찾기 쉽지 않을 수 있으니 통용되는 네이밍을 준수하자

 

 

결론

  • 객체 생성의 의미를 정확하게 부여하고 싶을 때 (부여해야할 때)
  • 객체 생성 과정을 캡슐화하고 싶을 때 (다양한 로직에 따라 다른 하위 자료형 반환 등) 
  • 인스턴스 생성 통제하고 싶을 때 (객체 생성을 위한 특별한 메서드 제공, new 연발 금지 -> 캐싱, 싱글톤 적용) 
  • 매개변수가 너무 많거나, 상속을 해야한다면 사용하지 말자 

참고자료 

https://javabom.tistory.com/1

 

아이템 [1] - 생성자 대신 정적 팩터리 메서드를 고려하라

인스턴스를 생성하는 방법은 (1) public 생성자 (2) 정적 팩터리 메서드가 있다. 여기서 정적 팩터리 메서드를 사용할 때 찾을 수 있는 장점은 다음과 같다. 1. 이름을 가질 수 있다. 생성자는 클래스

javabom.tistory.com

 

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90#%EC%83%9D%EC%84%B1%EC%9E%90_%EB%8C%80%EC%8B%A0_%EC%A0%95%EC%A0%81_%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C%EB%A5%BC_%EA%B3%A0%EB%A0%A4%ED%95%98%EB%9D%BC

 

💠 정적 팩토리 메서드 패턴 (Static Factory Method)

Static Factory Method Pattern 정적 팩토리 메서드(Static Factory Method) 패턴은 개발자가 구성한 Static Method를 통해 간접적으로 생성자를 호출하는 객체를 생성하는 디자인 패턴이다. 우리는 지금까지 객체

inpa.tistory.com