제리 devlog

[이팩티브 자바] 생성자 대신 static factory method 고려하라 본문

Java

[이팩티브 자바] 생성자 대신 static factory method 고려하라

제리 . 2020. 9. 18. 15:45

  • 장점  
  1. 네이밍을 통해 직관적으로 파악할 수 있다.
  2. 생성자의 매개변수 타입이 겹칠 경우 사용이 사용할 수 있다.
  3. 호출될 때마다 인스턴스를 생성하지 않아도된다.
  4. 반환 타입의 하위 타입으로 객체를 반환 할 수있다.
  5. static method를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.
  • 단점
  1. static method만 제공하면 하위 클래스를 만들 수 없다.
  2. static method는 프로그래머가 찾기 어렵다.

 

객체의 인스턴스를 얻기위해 보편적으로 public한 생성자를 사용하는 방법이 있다.

public class Fruit{

    int price;

    public Fruit(int price){
    	this.price = price;
    }

    public static void main(String[] args){
        Fruit fruit1 = new Fruit(500);
    }
}

 

이 방법대신에 static method를 사용해서 객체를 생성할 수 있다. 

public class Fruit{

    int price;

    public Fruit(int price){
        this.price = price;
    }

    public static Fruit withPrice(int price) { //객체를 생성하기 위한 static 메서드
        return new Fruit(price);
    }
    
    public static void main(String[] args){
        Fruit fruit2 = Fruit.withName(500);
    }
}

두 방식의 차이를 비교해보자

 

장점

1. 네이밍을 통해 직관적으로 파악할 수 있다.

public static void main(String[] args){
        Fruit fruit1 = new Fruit(500); //public 생성자를 이용한 방법
        Fruit fruit2 = Fruit.withPrice(500); //static 메서드를 사용한 방법
    } 

객체를 생성하는데 500이라는 인자가 사용된다. new Fruit(500)은 500이 직관적으로 어떤 것을 의미하는데 파악하기 쉽지않다. 반면, withPrice라는 static메서드를 사용한 방식은 의미가 명확히 전달된다.

 

2. 생성자의 매개변수 타입이 겹칠 경우 사용이 사용할 수 있다.

public class Fruit{

    String name;
    String from;

    public Fruit(String name){
        this.name = name;
    };

    public Fruit(String from){ //불가능
        this.from = from;
    };

}

생성자는 동일한 타입의 매개 변수를 가질 수 없다. static method를 사용하면 매개변수 형식이 겹치는 부분은 아래와 같이 처리할 수 있다.

public class Fruit{

    String name;
    String from;

    public Fruit(){}

    public Fruit(String name){
        this.name = name;
    };

    public static Fruit withFrom(String from){
        Fruit fruit = new Fruit();
        fruit.from = from;
        return fruit;
    };

    public static void main(String[] args){
        Fruit fruit = Fruit.withFrom("Korea");
    }
}

3. 호출될 때마다 인스턴스를 생성하지 않아도된다.

객체를 호출 될때마다 매번 생성하지 않고 싱글톤 형태로 객체를 유일하게 생성할 수 있다. 객체 생성에 비용이 큰 경우 성능 향상을 체감 할 수 있다. 

public class Fruit{

    public Fruit(){}

    private static Fruit fruit = null;

    public Fruit getFruit(){
        if(fruit == null){
            fruit = new Fruit();
        }
        return fruit;
    }

    public static void main(String[] args){
        Fruit fruit = Fruit.fruit.getFruit();
    }
}

4. 반환 타입의 하위 타입으로 객체를 반환 할 수있다.

public class Fruit {

    String name;

    public static Fruit apple(){
        Fruit fruit = new Apple();
        return fruit;
    };

    public static void main(String[] args){
        Fruit fruit = Fruit.apple();
    }
}

class Apple extends Fruit{
    public Apple(){
        super.name = "apple";
    }
}

Fruit클래스의 static 메서드를 통해 apple의 구현체를 얻을 수 있다. 

public class Fruit {

    String name;

    public static Fruit name(String name){
        Fruit fruit = null;
        switch (name){
            case "apple" :
                fruit = new Apple();
                break;
            case "banana" :
                fruit = new Banana();
                break;
            default:
                break;
        }
        return fruit;
    };

    public static void main(String[] args){
        Fruit fruit = Fruit.name("banana");
    }
}

class Apple extends Fruit{
    public Apple(){
        super.name = "apple";
    }
}

class Banana extends Fruit{
    public Banana(){
        super.name = "banana";
    }
}

위와 같이 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수도있다.

이런 유연함은 API를 만들때 구현 클래스를 공개하지 않고도 객체를 반환할 수 있어 API를 작게 유지할 수 있다.

collection구현체들

Collections은 static 메서드를 통해 아래와 같이 45개의 클래스를 제공한다.  컬랙션 프레임워크는 각각의 구현체를 공개하지 않았기 때문에 API의 외견이 작아졌다. 유저는 익혀야하는 API가 훨씬 적어진 것이다. 

5. static method를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.

jdbc의 경우 dbms 드라이버를 선택해야한다. 여기서 리플렉션의 개념이 등장한다.

리플렉션은 구체적인 클래스 타입을 알지 못하더라도 클래스의 필드와 메서드에 접근할 수 있도록 하는 API이다.

static 메서드는 클래스가 로딩될때 내용이 실행된다. 그렇기때문에 실행시점에서 드라이버를 선택할 수 있다.

jdbc에서는 코드가 실행되기 전까지 어떤 드라이버를 로드해야할지 알지 못한다. 이때 리플렉션을 사용해서 동적으로 드라이버를 로드하는 것이다.  이런 유연함이 서비스 제공자 프레임 워크를 만드는 근간이된다.

 

단점

1. static method만 제공하면 하위 클래스를 만들 수 없다.

상속을 하려면 public 혹은 protected 생성자가 필요하다. static method만 존재한다면 상속을 할 수 없다.

2. static method는 프로그래머가 찾기 어렵다.

Fruit fruit = new Fruit()

생성자를 통한 인스턴스 생성은 다른 클래스에 대해서도 new 클래스(매개 변수)형태로 정의된다.

하지만 static method의 경우 어떻게 네이밍되어있는지를 알아야 사용자가 사용할 수 있다. 이 부분에 대해 일반적으로 따르는 규약이 있다.

from : 매개 변수를 하나 받아 해당타입의 인스턴스를 반환하는 형변환 메서드
ex) Date date = Date.from(instant);

of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
ex) Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);

valueOf: from과 of의 더 자세한 버전
ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

instance 혹은 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하진 않음
ex) StackWaler luck = StackWalker.getInstance(options);

create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
ex) Object newArray = Array.newInstance(classObject, arrayLen);

getType: getInstance와 같으나, 생성한 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용. "Type"은 팩터리 메서드가 반환할 객체 타입.
ex) FileStore fs = Files.getFileStore(path);

newType: newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 팩터리 메서드가 반환할 객체의 타입.
ex) BufferedReader br = Files.newBufferedReader(path);

type : getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);

 

Comments