자바의 생성자와 정적 팩토리 메서드

2020. 4. 20. 22:20Java

생성자

일반적으로 클래스는 인스턴스 필드와 생성자를 갖는다. row와 column으로 이루어진 Position 클래스는 아래와 같이 표현할 수 있다.

public class Position {
    private final int row;
    private final int column;

    public Position(int row, int column) {
        this.row = row;
        this.column = column;
    }
}

만약 파라미터로 Int 뿐만 아니라 String도 받게 하고 싶다면 아래와 같이 코드를 추가할 수 있다.

public class Position {
    private final int row;
    private final int column;

    public Position(int row, int column) {
        this.row = row;
        this.column = column;
    }

    public Position(String row, String column) {
        this.row = Integer.parseInt(row);
        this.column = Integer.parseInt(column);
    }
}

하지만 이 코드는 코드 변경에 취약하다. row 또는 column의 타입이 변경되면 생성자를 모두 찾아서 변경해줘야 하기 때문이다. this 키워드를 사용하면 조금 더 변경에 안전한 코드를 짤 수 있다(이를 constructor chaining이라 한다).

public class Position {
    private final int row;
    private final int column;

    public Position(int row, int column) {
        this.row = row;
        this.column = column;
    }

    public Position(String row, String column) {
        this(Integer.parseInt(row), Integer.parseInt(column));
    }
}

파라미터로 Int 타입을 받는 주 생성자와 파라미터로 String 타입을 받는 부 생성자로 나눴다. 실제 초기화 로직은 주 생성자에만 위치하고 다른 부 생성자들에서는 주 생성자를 호출하는 방식으로 초기화가 이뤄진다. 따라서, 주 생성자는 클래스의 인스턴스 필드를 모두 포함하는 가장 간결하고 완전한 생성자여야 한다. 이 방식을 사용하면 중복 코드를 방지할 수 있고, 유지보수성이 향상된다.

그렇다면 한 클래스에는 몇 개 정도의 생성자가 존재하면 좋을까? 아마도 정답은 필요한만큼, 가능한 적게 일 것이다. 생성자의 숫자가 많을수록 클래스를 더 유연하게 사용할 수 있다. 하지만, 생성자가 너무 많다는 것은 클래스가 너무 많은 일을 하고 있다는 신호일 수 있다. 따라서 꼭 필요한만큼의 생성자 갯수를 유지하도록 하자.

정적 팩토리 메서드

이펙티브 자바 3판에서는 생성자 대신 정적 팩토리 메서드 사용을 권하고 있다. 이를 적용하면 앞선 코드는 다음과 같이 바뀐다.

public class Position {
    private final int row;
    private final int column;

    private Position(int row, int column) {
        this.row = row;
        this.column = column;
    }

    public static Position of(int row, int column) {
        return new Position(row, column);
    }

    public static Position of(String row, String column) {
        return new Position(Integer.parseInt(row), Integer.parseInt(column));
    }
}

이펙티브 자바에서는 정적 팩토리 메서드의 장점을 다음과 같이 소개하고 있다.

  • 이름을 가질 수 있다.
    new BigInteger(int, Random)보다 BigInteger.probablePrime(int, Random)이 소수를 반환한다는 뜻을 더 잘 나타낸다. 단순 생성자에 비해서 가독성이 향상되는 효과가 있다.

  • 호출 시마다 인스턴스를 생성하지 않을 수 있다
    new 키워드는 비싸다. 자주 사용하지만 매번 생성할 필요가 없는 인스턴스들은 캐싱 기법을 사용해 매 호출마다 단순히 불러오기만 하면 된다. Integer.valueOf()는 자주 사용하는 Integer(-128 ~ 127)에 대하여 미리 인스턴스를 생성해두고, 필요할 때 호출할 수 있도록 IntegerCache라는 내부 클래스를 활용하고 있다. 이처럼 생성 또는 호출을 메서드 뒤에 감출 수 있다는 장점이 있다.

  • 반환 타입의 하위 객체를 반환할 수 있다.
    팩토리 메서드의 반환 타입이 인터페이스일 경우, 내부 구현을 숨긴 채 인터페이스를 생성해 반환할 수 있다. 이를 활용하면 유연성낮은 결합도를 얻을 수 있다.

  • 입력 매개 변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
    바로 전의 장점과 거의 같은 장점이다.

    public interface AnimalSound {
        void said();
    }
    
    class Dog implements AnimalSound {
        @Override
        public void said() {
            System.out.println("wal wal");
        }
    }
    
    class Cat implements AnimalSound {
        @Override
        public void said() {
            System.out.println("mi-yao");
        }
    }
    
    class Animal {
        public static AnimalSound from(String name) {
            if (name.equals("dog")) {
                return new Dog();
            }
            if (name.equals("cat")) {
                return new Cat();
            }
            ...
        }
    }

    위 예시에서 Animal.from()을 호출하는 쪽에서는 어떤 구현체가 생성되는지 모른 채, 인터페이스만을 반환받는다.

  • 정적 팩토리 메서드를 작성하는 시점에 실제 구현체가 없어도 된다.

    이를 활용한 것이 JDBC 규격에 맞게 Driver와 Connection 인터페이스를 구현한 DBMS들이다. JDBC의 팩토리 메서드들은 실제 구현체가 없지만 각 DBMS 회사에서 이에 맞게 구현체를 구현해서 제공하면, 사용자가 이를 사용할 수 있게 된다.

 

단점에 대해서도 소개하고 있다. 하나는 상속이 불가능해진다는 점과 프로그래머가 바로 알아볼 수 없다는 점이다. 하지만 상속을 못하게 하는 것은 오히려 장점으로 생각할 수 있고, 명확한 네이밍 등을 통하여 의도도 전달할 수 있으니 딱히 단점이라고 보기 어렵다. 예전에 정적 팩토리 메서드에 대해서 비판하는 글을 찾은 적이 있는데, 다시 검색해봐도 찾을 수가 없다... 

 

 

다음은 정적 팩토리 메서드의 명명 규칙이다.

참고 사이트

https://www.geeksforgeeks.org/constructor-chaining-java-examples/

https://stackoverflow.com/questions/490661/how-many-constructors-should-a-class-have

https://umanking.github.io/effective%20java/2020/02/18/effective-java-1.html

https://devyongsik.tistory.com/294