2020. 4. 20. 22:20ㆍJava
생성자
일반적으로 클래스는 인스턴스 필드와 생성자를 갖는다. 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
'Java' 카테고리의 다른 글
[Spring Data JDBC] 객체(Object)와 개체(Entity) 대응시키기 (0) | 2020.05.11 |
---|---|
JPA vs JDBC, JPA vs Mybatis, JPA vs Spring Data JPA의 차이점과 Hibernate (0) | 2020.04.25 |
Intelij 단축키 - 알아두면 생산성이 올라가는 단축키 모음 (0) | 2020.04.20 |
자바의 Enum (1) | 2020.04.18 |
자바의 예외처리 (0) | 2020.04.18 |