들어가기 전
해당 내용은 지극히 주관적이어서, 사람마다 생각이 다를 수 있는 부분이다.
그래서 이게 정답이라고 생각하기 보다,
이 사람은 이렇게 생각하는구나! 정도로 이해하면 될 것 같다!
0. 고민 상황
우선, 현재 미션 구조에서 MVC 패턴을 사용하고 있다.
또한, 원시 값을 포장하라는 요구사항을 받아서 여러 포장 클래스들이 존재한다.
Player 객체는 이름을 가지고 있고, 해당 이름은 문자열 String이 아닌
포장 클래스인 Name을 가진다.
고민 상황은, Player의 Name을 View에 전달하고자 한다.
이때, 어떻게 Player의 Name을 View에게 전달해야할까?
1. 포장 값을 View로 전달하는 방식
MVC패턴에서 포장 값을 View로 전달하는 방식으로는 다음과 같이 2가지 방식이 있다.
1. 포장 클래스를 View로 넘겨서 View에서 포장을 풀어서 사용하는 방식
2. 외부(Controller, Service)에서 포장을 풀어서 원시 값을 View에 넘겨주는 방식
위의 2가지 방식들을 어떻게 사용하는지 예시 코드로 알아보자.
먼저 공통적으로 포장 클래스인 Name 코드는 다음과 같다.
※ Name 클래스
package ladder.domain.player;
import java.util.List;
public class Name {
private String name;
public Name(String name) {
this.name = name;
}
public String getRawName() {
return name;
}
}
단순하게 문자열 name 필드가 있고, 해당 필드를 View에 전달하기 위해 getter를 가진다.
View에 전달되기 위해서 Name은 다음과 같은 흐름을 가진다.
Name -> Player -> Controller -> View
이때, Controller에서 View에게 전달하는 코드에 방식에 따른 차이가 있다.
1-1. 포장 클래스를 View로 넘겨서 View에서 포장을 풀어서 사용하는 방식
※ Controller 클래스
public class Controller {
private final Player player;
private final OutputView outputView;
...
public void printPlayerNameStep() {
Name name = player.getName();
outputView.printPlayerName(name);
}
...
}
Controller에서 printPlayerNameStep()을 호출할 때 Name 클래스를 바로 View에 전달한다.
※ Player 클래스
public class Player {
private final Name name;
public Name getName() {
return this.name;
}
}
Controller에서 Name 클래스를 바로 View에 전달해야하기 때문에
Player에서 Name을 넘길 때 포장 클래스를 그대로 넘긴다.
※ 출력 담당 OutputView 클래스
public class OutputView {
public void printPlayerName(Name name) {
String rawName = name.getRawName();
System.out.println(rawName);
}
}
사용하는 View에서는 포장 클래스를 직접 받아서 메소드 내부에서 포장을 풀어서 사용한다.
1-2. 외부(Controller, Service)에서 포장을 풀어서 원시 값을 View에 넘겨주는 방식
※ Controller 클래스
public class Controller {
private final Player player;
private final OutputView outputView;
...
public void printPlayerNameStep() {
String name = player.getName();
outputView.printPlayerName(name);
}
...
}
Controller에서 printPlayerNameStep()을 호출할 때
Player 내부에서 Name 포장을 풀어서 문자열로 받은 후에 View에 전달한다.
※ Player 클래스
public class Player {
private final Name name;
public String getName() {
return this.name.getRawName();
}
}
Controller에서 포장을 푼 문자열을 View에 전달해야하기 때문에
Player에서 Name을 넘길 때 포장을 푼 문자열을 반환한다.
※ 출력 담당 OutputView 클래스
public class OutputView {
public void printPlayerName(String name) {
System.out.println(name);
}
}
사용하는 View에서는 포장 클래스가 아닌 문자열을 받아서 바로 출력한다.
2. 두 가지 방식의 차이
1. 포장 클래스를 View로 넘겨서 View에서 포장을 풀어서 사용하는 방식
2. 외부(Controller, Service)에서 포장을 풀어서 원시 값을 View에 넘겨주는 방식
핵심적으로,
Player의 getName() 코드와 OutputView의 printPlayerName() 코드에 차이가 있다.
2-1. Player의 getName()
public class Player {
private final Name name;
// 1번 방식 - 그대로 Name을 넘긴다.
public Name getName() {
return this.name;
}
// 2번 방식 - 포장을 풀어서 String을 넘긴다.
public String getName() {
return this.name.getRawName();
}
}
1번 방식을 사용하면, 그대로 필드값을 넘기므로 고민 거리가 없을 것 같다.
하지만, 2번 방식을 사용한다고 했을 때 처음 들었던 생각은
'결국 Name도 Player가 아닌 다른 도메인 객체인데 다른 도메인 객체의 getter를 쓰는 것이 아닌가?' 였다.
따라서, '객체지향'의 관점에서 문제가 있을 것이라고 생각했다.
이를 리뷰어님께 질문드렸더니
'Name은 Player의 이름을 위해 만들어진 값 객체'이므로
Player가 getter로 Name의 포장을 풀어서 반환하는 것이 이상하지 않다.
오히려 외부에서 Name의 getter를 호출해야하는 번거로움을 줄일수 있다! 라고 답변을 주셨다.
답변을 듣고 생각해보니 Player는 이미 필드로 Name을 알고 있고,
의존이 되어있기 때문에 getter를 쓰는 것이 괜찮다고 생각이 들게 되었다.
2-2. OutputView의 printPlayerName()
public class OutputView {
// 1번 방식 - Name을 받아서 내부에서 포장을 풀어서 사용
public void printPlayerName(Name name) {
String rawName = name.getRawName();
System.out.println(rawName);
}
// 2번 방식 - 포장을 푼 String을 받아서 사용
public void printPlayerName(String name) {
System.out.println(name);
}
}
먼저 2번 방식으로 사용하면, View 입장에서 객체가 아닌 String을 받기 때문에
아무런 고민 거리가 없을 것 같다.
반면에 1번 상황은, View 입장에서 '모델 객체'를 받고 있다.
Model에서 직접 받은 것이 아니라, Controller에게 전달받은 것이므로
MVC 패턴에서 특별히 위배한 것은 없다.
하지만, 도메인 객체가 수정되었다고 생각해보자.
예를 들어, Name의 클래스 네이밍을 구체적으로 하고 싶어서 'PlayerName'으로 수정하고 싶다고 하자.
이렇게 수정할 때, 1번 방식이었으면 도메인 객체를 수정했는데, View단 수정도 일어나야한다.
따라서 View가 Model 객체를 의존하고 있는 것이다.
3. 내가 사용하는 방식
1. 포장 클래스를 View로 넘겨서 View에서 포장을 풀어서 사용하는 방식
2. 외부(Controller, Service)에서 포장을 풀어서 원시 값을 View에 넘겨주는 방식
나는 외부에서 포장을 풀어서 View에 넘겨주는 2번 방식을 사용했고, 사용할 것 같다.
앞에서 두 방식의 차이를 봤었을 때
2번 방식에서는 Model 객체에서 포장 클래스를 다른 Model 객체로 바라봐서
다른 Model 객체를 참조해서 getter를 사용한다는 찝찝함이 있었지만
결국 포장 클래스는 Model 객체가 이미 알고 있는, 의존하고 있는 Model 객체이기 때문에
getter를 사용해도 된다고 리뷰어님한테 설득당해버렸다.
그리고 무엇보다 1번 방식의 단점이 더 크게 와닿았던 것 같다.
포장 클래스인 Model 객체이긴 하지만
View 단에서 Model 객체를 의존하고 있다는 단점을 가지고 있기 때문에
1번 방식을 사용하지 않을 것 같다.
물론, 포장 클래스는 로직을 가지지 않고 단순히 포장 클래스로 여기고
핵심 비즈니스 로직을 가진 Model을 의존하는 것이 아니라고 생각할 수도 있다.
현재 Name의 예시에서도 단순히 값만 포장할 뿐, 다른 로직은 없다.
하지만 이름의 검증 로직이 들어간다면?
Player에 들어가는 것이 아니라 포장 클래스인 Name에 들어가게 될 것이다.
단순히 출력만 담당하는 View에서 Name의 검증 로직까지 알아야 할까?
이러한 부분에서 나는 외부에서 포장을 풀어서 원시 값을 View에 넘기는 2번 방식을 사용할 것 같다!
이번 내용은 상당히 주관적으로 작성을 해서
포장 클래스를 직접 View에서 사용하는 방식의 단점만 말한 것 같아 아쉽다.
'우아한테크코스 5기 > 학습 로그' 카테고리의 다른 글
함수형 인터페이스의 정의 & 사용 예제 (0) | 2023.04.10 |
---|---|
instanceof 사용을 지양하기 - Why? Solution? (1) | 2023.03.19 |
VO(Value Object)는 무엇일까? 왜 사용할까? (2) | 2023.03.12 |
도메인에 처음 접근하기 - Out -> In / In -> Out 접근 방식 (0) | 2023.02.27 |
[Java] ArrayList, LinkedList를 직접 구현해보며 이해한 것 (0) | 2023.02.26 |