지금까지 인스턴스를 생성할 때 생성자만 사용했던 것 같다. 사실 이 책을 읽으면서 정적 팩토리 메소드를 처음 접해서 두 개의 차이를 먼저 구분해보고자 한다.
생성자 : 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메소드', 인스턴스 변수의 초기화 작업에 사용되고, 인스턴스 생성 시에 실행,
new
방식
코드로 확인 해보자
public class Test {
private String name;
public Test(String name) {
this.name = name;
}
}
정적 팩토리 메소드 : 객체 생성을 담당하는 클래스 메소드, 흔히 사용하는 생성자가 아니라 정적 메소드로 생성하는 방법
public class Test {
private String name;
public Test(String name) {
this.name = name;
}
public static Test withName(String name) {
return new Test(name);
}
public static void main(String[] args) {
Test test = withName("Gyunny");
}
}
생성자를 통해 객체를 생성하는 것이 기본이지만 생성자는 명명을 할 수 없어 생성자 안에 별도의 전처리 로직이 있을 경우 해당 전처리에서 무엇을 하는지 "직접" 확인해야한다.
public class Pizza {
private final String name;
private final String cheese;
public Pizza(String name, String cheese) {
this.name = name;
this.cheese = cheese;
}
public Pizza(String name){
this.name = name;
this.cheese = "치즈 없음";
}
}
public static void main(String[] args) {
Pizza cheese = new Pizza("yesCheese", "모짜렐라");
Pizza olive = new Pizza("noCheese");
}
위 코드를 확인하면 치즈가 없는 피자를 만들 때 어떤 방식으로 Pizza 클래스에서 치즈를 할당하는지 확인이 불가 하다.
public class Pizza {
private final String name;
private final String cheese;
public static Pizza makePizza(String name, String cheese) {
return new Pizza(name, cheese);
}
public static Pizza makeNoCheesePizza(String name) {
return new Pizza(name, "치즈 없음");
}
private Pizza(String name, String cheese) {
this.name = name;
this.cheese = cheese;
}
}
public static void main(String[] args) {
Pizza cheesePizza = makePizza("치즈 피자", "모짜렐라");
Pizza noCheesePizza = makeNoCheesePizza("치즈 없는 피자");
}
생성자로 객체를 생성했을 때에 비해 메소드 명으로 어떤 객체일 지 쉽게 유추할 수 있다.
플라이 웨이트 패턴(Flyweight pattern) : 데이터를 공유하여 메모리를 절약하는 패턴, 공통으로 사용되는 객체는 한 번만 사용되고 Pool에 의해서 관리, 사용되는 방식 즉, 인스턴스를 가능한 만큼 공유하게 하여 쓸데 없이 new 연산자를 통한 메모리 낭비를 줄이는 방식
public class Day {
private static final Map<String, Day> days = new HashMap<>();
static {
days.put("mon", new Day("Monday"));
days.put("tue", new Day("Tuesday"));
days.put("wen", new Day("Wednesday"));
days.put("thu", new Day("Thursday"));
days.put("fri", new Day("Friday"));
days.put("sat", new Day("Saturday"));
days.put("sun", new Day("Sunday"));
}
public static Day from(String day) {
return days.get(day);
}
private final String day;
private Day(String day) {
this.day = day;
}
public String getDay() {
return day;
}
}
public static void main(String[] args) {
Day day = Day.from("mon");
System.out.println(day.getDay());
}
코드의 유연성을 제공해준다. 반환할 객체의 자유도를 높일 수 있고 원하는 객체를 리턴 할 수 있다.
코드로 확인해보자~!
public abstract class StaticFactoryMethod {
abstract void getName();
static public StaticFactoryMethod getNewInstance(String code) {
StaticFactoryMethod staticFactoryMethod = null;
if (code.indexOf("2") == 1) {
staticFactoryMethod = new Point();
} else {
staticFactoryMethod = new Coupon();
}
return staticFactoryMethod;
}
}
class Coupon extends StaticFactoryMethod {
public void getName() {
System.out.println("쿠폰을 발행합니다!");
}
}
class Point extends StaticFactoryMethod {
public void getName() {
System.out.println("포인트를 적립합니다!");
}
}
public class Main {
public static void main(String[] args) {
StaticFactoryMethod staticFactoryMethod = StaticFactoryMethod.getNewInstance("223123");
StaticFactoryMethod staticFactoryMethod1 = StaticFactoryMethod.getNewInstance("123123");
staticFactoryMethod.getName();
staticFactoryMethod1.getName();
}
}
위에 예시와 같이 코드에 따라 반환 객체를 자신의 하위 타입 중에 선택이 가능하다.
장점 3과 비슷한 의미이다.
예를 들면 같은 이름의 메소드지만 매개변수에 따라 다른 클래스를 리턴할 수 있다. -> 코드의 유연성이 높아진다.
특정 인터페이스들을 상속받은 구현체들이 있을 때, 객체 생성 시 상황에 따라 유동적으로 해당 구현체 타입으로 반환 가능!!
public abstract class StaticFactoryMethod {
abstract void getName();
static public StaticFactoryMethod getNewInstance(String code, String coupon) {
return new Coupon();
}
static public StaticFactoryMethod getNewInstance(String code) {
return new Point();
}
}
class Coupon extends StaticFactoryMethod {
public void getName() {
System.out.println("쿠폰을 발행합니다!");
}
}
class Point extends StaticFactoryMethod {
public void getName() {
System.out.println("포인트를 적립합니다!");
}
}
public class Main {
public static void main(String[] args) {
StaticFactoryMethod coupon = StaticFactoryMethod.getNewInstance("223123", "coupon");
StaticFactoryMethod point = StaticFactoryMethod.getNewInstance("123123");
coupon.getName();
point.getName();
}
}
이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다. -> JDBC 대표적
이게 무슨 말일까...?
당장 클래스나 인터페이스가 존재하지 않아도 특정 텍스트 파일에 인터페이스 구현체의 위치를 알려주는 곳의 정보를 가지고 해당 객체를 읽어 생성할 수 있다.
하위 타입의 클래스가 존재하지 않아도 나중에 만든 클래스가 기존의 인터페이스나 클래스를 상속 받으면 언제든지 의존성을 주입 받을 수 있다.
코드로 확인해보자...!
public abstract class StaticFactoryMethod5 {
public abstract void getName();
public static StaticFactoryMethod5 getNewInstance() {
StaticFactoryMethod5 temp = null;
try {
Class<?> childClass = Class.forName("StaticFactoryMethod5Child");
temp = (StaticFactoryMethod5) childClass.newInstance();
} catch (ClassNotFoundException e) {
System.out.println("클래스가 없습니다.");
} catch (InstantiationException e) {
System.out.println("메모리에 올릴수 없습니다.");
} catch (IllegalAccessException e) {
System.out.println("클래스파일 접근 오류입니다.");
}
return temp;
}
}
public class StaticFactoryMethod5Child extends StaticFactoryMethod5{
@Override
public void getName() {
System.out.println("정상 로드 되었습니다~!~");
}
}
public class Main {
public static void main(String[] args) {
StaticFactoryMethod5 staticFactoryMethod5 = StaticFactoryMethod5.getNewInstance();
staticFactoryMethod5.getName();
}
}
정적 팩토리 메소드만 사용하게 하려면 기존 생성자는 private 으로 해야하고 상속할 수 없다.
new 를 통한 인스턴스화는 모든 개발자가 알고 있지만, 메소드를 통해 제공한다면 찾아야한다.
개발자가 임의로 만든 정적 팩토리 메소드 특성상, 다른 개발자들이 사용 시 정적 팩토리 메소드를 찾기 어려울 수 있다.