프로젝트/우테코 6기

[1주차/숫자야구] 3. getter을 사용하는 대신 객체에 메시지를 전달하기

zangsu_ 2023. 10. 23. 14:57

이번 포스팅은 리팩토링 과정을 담았다.

 

리팩토링 이전

리팩토링을 진행하기 전의 한 번의 게임 흐름을 간단하게 살펴보자.

//Game.java

public void play() {
    Result result;
    computer.init();
    do {
        System.out.print("숫자를 입력해주세요 : ");
        List<Integer> input = player.getInput();
        result = computer.getResult(input);
        System.out.println(result);
    } while (result.getStrike() != 3);
    System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
}

 

우리는 Computer 객체와 Player 객체의 각각의 메서드를 호출하며 전체 흐름을 이어 나갈 것이다.

사용자의 입력을 List<Integer> 형식으로 받아온 후, Computer 객체에게 해당 입력에 대한 결과를 반환 받는다.

만약, 결과 객체의 strike 변수 값이 3이라면, 즉 3스트라이크 라면 정답을 맞춘 상황이라 판단하여 게임을 종료시킨다.

 

무엇이 문제일까...?

사실 논리적인 흐름은 문제가 없었다. 

다만, 프리코스 커뮤니티를 보던 중, "getter를 사용하는 대신, 객체에 메시지를 보내자." 라는 글을 읽게 되었다.

테코블의 글을 공유한 것이었는데, 아래에 링크를 첨부해 둔다.

 

https://tecoble.techcourse.co.kr/post/2020-04-28-ask-instead-of-getter/

 

getter를 사용하는 대신 객체에 메시지를 보내자

getter는 멤버변수의 값을 호출하는 메소드이고, setter는 멤버변수의 값을 변경시키는 메소드이다. 자바 빈 설계 규약에 따르면 자바 빈 클래스 설계 시, 클래스의 멤버변수의 접근제어자는 private

tecoble.techcourse.co.kr

 

결국, 내가 각각의 클래스를 구분해 둔 것은 "객체들간의 협업을 통해 프로그램을 구성"하기 위한 것이었다.

즉, 객체들은 각각 자신의 책임을 다해야 한다.

이런 관점에서, getter를 통해 객체의 정보를 외부에서 접근하게 하고, 외부에서 getter를 통해 받아 온 정보로 어떤 결정을 하게 된다면 이것은 "객체 답지 않은 객체"를 만들었을 가능성이 생기는 것이다.

 

무슨말이냐면,,,

내가 위의 play() 로직의 while문에서 사용하고 싶은 조건은 "사용자의 입력 결과가 정답인가?"를 판단하여, 정답이 아닌 동안 while 문을 반복하는 것이다.

그렇다면, 사용자의 입력이 정답인지를 판단하는 역할은 어떤 객체에게 있을까?

나는 Result 객체 자체가 결과를 나타내기 위한 객체이므로, 이 역할 또한 Result 객체에게 책임이 있다고 보았다.

그렇기에 Result 객체의 strike 변수 값을 가져와, Game 클래스에서 정답을 판단하는 것은 Game 클래스에게 필요 이상의 역할을 부여한 것이다.

 

리팩토링

그렇다면, 우리가 할 일은 간단하다.

Result 클래스가 스스로 자신이 가지고 있는 결과가 정답인지를 판단하는 메서드를 제공해 주면 되는 것이다.

 

public class Result {

    private int strike;
    private int ball;
    //== 생략 ==//
    public boolean isAnswer() {
        return this.strike == 3;
    }
}
public void play() {
    Result result;
    computer.init();
    do {
        System.out.print("숫자를 입력해주세요 : ");
        List<Integer> input = player.getInput();
        result = computer.getResult(input);
        System.out.println(result);
    } while (!result.isAnswer()); //getter를 쓰는 대신, Result가 제공하는 메서드 사용
    System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
}

위와 같이 getter를 사용하는 대신 Result.isAnswer() 메서드를 사용해 정답을 판단하는 책임을 Result에게 부여했다.

 

정리

당연히, getter를 완전히 사용하지 말라는 것은 아니다.

다만, getter의 사용을 지양하는 것이 아래와 같은 장점을 가져다 준다.

  • 의도치 않은 값의 변경을 막을 수 있다.
    • getter를 통해 멤버 객체를 가지고 올 수 있다면, 외부에서 해당 객체의 필드 값을 변경시킬 수도 있다.
    • getter의 사용을 지양하여, 내부의 필드 값을 외부로 유출시키지 않음으로써 사이드 이펙트를 방지할 수 있다는 것이다.
  • 특정 객체의 책임을 다른 객체에게 위임하지 않는다.
    • A 객체의 getter를 통해 A 객체가 특정 조건을 만족하는 것은 사실, getter를 호출하는 객체의 책임이 아닐 가능성이 높다.
    • 포스팅에서의 흐름과 같이, 각 객체가 스스로의 책임을 전부 가지도록 하여 객체지향의 설계에 가까워 질 수 있다.