TIL 2021.02.19

Kyu·2021년 2월 19일
0

TIL

목록 보기
40/322

🧐 TIL (Today I Learned)

1. 정적 팩토리 메서드와 생성자를 통한 객체 생성

미션의 요구사항에서는 다음과 같이 설명되어있다.

Pieces는 값 (value) 객체여야 한다. 즉, private 생성자를 가져야한다. 인스턴스를 생성한 이후에는 인스턴스의 상태 값을 변경할 수 없어야 한다.

이 부분이 마치 무조건 private 생성자를 가져야하는 것처럼 들리는데 아래와 같이 public 생성자를 가져도 Value Object를 만들 수 있다

final class Name {
    private String value;

    public Name(String value) {
        this.value = value;
    }
}

실제로 IDE에서 인스턴스를 만들어서 value에 접근하려고하면 아래와 같이 에러난다.

왜 굳이 정적 메서드를 사용하라고 했을까? 그냥 정적메서드가 아니라 생성자가 private이니까 정적 팩토리 메서드 쓰라고 하는 뜻이다.

생성자 VS 정적 팩토리 메소드

String str = new String("kyu");
String str = String.valueOf("kyu");

기본적으로 둘을 코드로 사용할 때는 위와 같은 차이를 가진다. 두번쨰코드는 String의 valueOf() 라는 정적 메소드로 kyu 라는 문자열을 생성한 것이다.

언제 무엇을 사용해야 할까?

생성자를 통하지 않고 정적 팩토리 메서드를 사용하는 이유는 뭘까? 이펙티브 자바 1장에는 “Consider static factory methods instead of constructors” 라고 나온다고 한다. 그만큼 어떤 이점이 있기 때문에 정적 팩토리 메서드를 사용하는 것이다. 그렇다고 앞으로 생성자 대신에 전부 정적 팩토리 메서드를 사용하라는 말은 아닐 것이다.

정적 팩토리 메서드 이해하기

// 코드 1

public class User {
    
    private final String name;
    private final String email;
    private final String country;
    
    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }
    
    // standard getters / toString
}

코드 1은 그냥 일반적으로 생성자로 데이터를 받아서 객체를 생성하는 방식이다. 딱히 문제가 없어보인다.

그런데 country만 기본값으로 모두 Korea 를 가지게 하고 싶다면 어떨까? country를 상수로 바꾸고 생성자를 수정하던지, 애초에 생성자에 Korea 라고 붙이던지 어쨋든 리팩토링이 필요할 것이다.

아니면, 이때 코드 2처럼 정적 팩토리 메서드를 사용할 수 있다.

// 코드 2

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Korea");
}

그리고 이 정적 팩토리 메소드를 사용해서 country 값이 Korea 로 할당된 객체를 다음 코드 3처럼 생성할 수 있다.

// 코드 3

User user = User.createWithDefaultCountry("Kyu", "kyu@gmail.com");

정적 팩토리 메서드 - 생성자 밖으로 로직을 옮기기

코드 1에서 봤던 User 클래스에서 어떤 기능을 구현하기 위해 생성자에 로직을 넣도록 요구되는 그런 일이 발생한다면, User 클래스는 결함이 있는 디자인으로 빠르게 썩어갈 것이다.

구체적으로 그런 일은 어떤 일인가? 예를 들어서 User 객체가 생성됐을 때마다, 생성된 시각을 기록하는 기능을 추가해주고 싶다면 어떨까?

만약에 생성자에 그 로직을 넣는다면 단일 책임 원칙을 어기게 된다. 단지, 필드를 초기화해주는 작업이 아니라 그보다 훨씬 더 많은 일을 하는 로직을 넣어줌으로써 굉장히 단단하게 결집되어 있는 생성자를 만들게 될 것이다.

이 때, 정적 팩토리 메서드를 사용해서 이 문제를 해결할 수 있다.

// 코드 4

public class User {
    
    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;
    
    // standard constructors / getters
    
    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }
}

정적 팩토리 메서드로 만들면서 생성자에 직접 로직을 넣는 형식보다 느슨한 형태의 결합 코드를 가지게 됐다.

다음 코드 5처럼 인스턴스를 생성할 수 있을 것이다.

// 코드 5

User user 
  = User.createWithLoggedInstantiationTime("Kyu", "kyu@gmail.com", "Korea");

참고
Java Constructors vs Static Factory Methods


📚일기

담부터 스크럼 할 때, 개념적인 이야기를 할 때는 처음에 그 개념이 간단하게 짚고 넘어가야겠다. 상대방이 알고있는 개념인지 참고해서 대화를 해야지.


✅투-두

내일도 미션 4 진행.

나중에 해야 할 To-do 링크

profile
TIL 남기는 공간입니다

2개의 댓글

comment-user-thumbnail
2021년 2월 20일

오 Kyu 되게 정리를 잘해 놓으셨네요.
객체지향 원리 원칙과 연계 지어서 설명한 부분이 되게 좋은 것 같네요.
제 개인적인 생각으로는 정적 팩토리 메소드 자체가 큐가 설명한 이점들도 있지만,
객체를 만들어서 주는 메소드 라는게 또 다른 관심사가 아닐까 라는 생각이 들어요
만약 테스트를 하기 위해 다른 사람이 내 코드를 이용해야 할때. (이번 체스미션을 예로 들면)
createWhitePawn() 이 라는 메소드를 쓰게되면, Pawn 이 어떤 속성을 가지고 있는지 몰라도 되잖아요?
그니까 생성자에 어떤 값을 넣어줘도 되는지 모르는데도 객체를 이용할 수 있을 것 같아요.
그래서 저희 코드에서는 정적 팩토리 메소드가 이러한 생성자로 만들어서 쓰는 객체를 공급하는게 아니라,
내가 지정한 값으로 객체를 공급하겠다. 라는 또 하나의 뜻이지 않을까 싶어요.
그렇기에 private 으로 생성자로 인한 객체 생성을 제어한거 같다는 느낌?
말이 길어졌는데 간단히 얘기하자면 객체를 생성해서 쓰는것과 제공 받아서 쓰는것은 다른 관심사이기에 저러한 정적 팩토리 메소드가 이용되는 것이 아닐까? 라는 생각에 적어보았습니다.

1개의 답글