본문 바로가기

Design Pattern

[Design Pattern] MVC 패턴 (1)

728x90
각 객체들의 설계를 고려하기 전 어플리케이션의 큰 구조를 어떻게 잡으면 좋을까?

개요

우테코 프리코스 미션에서 콘솔 어플리케이션을 구현하는 1주 차 미션을 진행했다.

콘솔을 통해 입력을 받고, 콘솔을 통해 결과를 보여주는 어플리케이션이다 보니 MVC 패턴이 유용했다.

 

MVC패턴은 Model, View, Controller 크게 3 가지의 역할로 어플리케이션의 구조를 나누는 것을 말한다.

각각의 역할과 내가 고민했던 것들을 기록하려고 한다.

 

우테코 프로코스 미션이었던 숫자 야구 게임을 예로 들어 MVC 패턴을 어떻게 적용하였는지 보자.

Model

책임

Model은 어플리케이션에서 특정 객체의 정보에 대한 가공의 책임이 있다.

특정 데이터와 그 데이터에 대한 비즈니스 로직을 담당한다.

 

숫자 야구 게임에서 정답 숫자를 가지고 유저에게 답해주는 상대(컴퓨터) 객체가 바로 Model에 해당된다.

정답 숫자를 가지고 있고 사용자의 추측에 대한 답을 계산하는 역할을 할 것이다. 

 

오직 특정 데이터와 관련 있는 역할을 수행하기 때문에

게임이 어떻게 진행되는지, 결과 또는 입력이 어떻게 보이는지에 대해서는 관여하지 않아야 한다.

고민한 점

Model에서는 View에 대한 로직을 분리하는 데 신경을 많이 썼다.

입력과 출력에 대해 Model은 최대한 관여하지 않도록 설계했고, 때문에 dto와 Mapper를 활용했다.

 

dto(Data Transfer Object)

계층 간 데이터 전송을 위 model 대신 사용되는 객체

dto를 활용한 이유는 앞서 말한 대로 view에 대한 로직을 분리하기 위함이었다.

 

만약 입력을 이용해 model을 생성해야 한다고 해보자.

이때 입력이 특정 문자로 구분되어 있다면, 그 문자를 model에서 알고 있어야 할까?

추후에 구분자를 변경해야 한다고 할 때 어디서 그 문자를 수정하는 것이 자연스러울까?

나는 model에서 수정하는 것은 자연스럽지 않다고 판단했다.

 

위와 같은 이유로 dto를 활용하게 되었다.

구분자와 같은 view 관련 로직을 이용한 model 생성에 필요한 list를 만드는 작업, model 데이터를 구분자를 통해 문자열로 바꾸는 등

View와 model에 대한 직렬화, 역직렬화 로직만을 담당하는 객체

public class UserNumbersDto {

    private static final String NUMBERS_SPLITTER = "";

    private final String userNumbers;

    public UserNumbersDto(final String userNumbers) {
        this.userNumbers = userNumbers;
    }

    public List<String> toStringNumbers() {
        return Arrays.stream(userNumbers.split(NUMBERS_SPLITTER))
            .toList();
    }
}

 

dto를 활용하게 되면, model과 view에서의 역할 분리가 확실히 될 수 있다.

 

Mapper

만약 model을 dto로 변환하는 과정에서 직렬화, 역직렬화 외 로직이 필요하다면 어떻게 해야 할까?

 

숫자 야구 게임을 예로 들면, 사용자의 추측에 대해

1볼 1스트라이크

위와 같은 식으로 View에서 보여줘야 한다.

 

그렇다면 결과 객체를 String으로 반환해야 하는데 이는 출력 관련 로직을 가질 것이다.

이 역할을 View에서 하면 Model을 알아야 하고, Model에서 하면 View를 알아야 하니 애매한 상황이다.

 

나는 Mapper 객체로 이 문제를 해결했다.

public class GuessResultMapper {

    private static final int INITIAL_ZERO_COUNT = 0;
    private static final String NOTHING_RESULT = "낫싱";
    private static final String BALL_RESULT = "볼";
    private static final String STRIKE_RESULT = "스트라이크";

    public static String resultToString(final GuessResult result) {
        if (result.isNothing()) {
            return NOTHING_RESULT;
        }
        int ballCount = result.getBallCount();
        int strikeCount = result.getStrikeCount();

        if (ballCount == INITIAL_ZERO_COUNT) {
            return strikeCount + STRIKE_RESULT;
        }

        if (strikeCount == INITIAL_ZERO_COUNT) {
            return ballCount + BALL_RESULT;
        }

        return ballCount + BALL_RESULT + " " + strikeCount + STRIKE_RESULT;
    }
}

Mapper 객체에서 특정 model 출력에 대한 로직을 담당해 두 객체의 역할 분리를 할 수 있었다.

View

책임

입출력되는 인터페이스를 담당한다. 따라서 비즈니스 로직, model에 관여해선 안 된다.

숫자 야구 게임에서 게임에 대한 안내를 하고, 입력을 받고, 결과를 출력하는 객체들이 View에 해당한다.

Controller

책임

model과 view의 중간다리 역할을 한다.

model에서 받은 데이터를 dto 처리해서 view에게 전달하거나 view에서 입력받은 문자열로 model을 생성하는 등의 역할을 한다.

 

숫자 야구 게임에서는 전체적인 게임을 진행하고, 입력 dto로 객체를 만들고,

model로 출력 dto 또는 문자열을 만드는 객체가 Controller에 해당한다.

고민한 점

위 dto와 mapper를 활용하는 것과 관련해 혼란스러웠던 것이 있었다.

미션을 진행하기 전, 스프링과 NestJS로 프로젝트를 하다 보니 레이어드 아키텍처에 익숙해 있었다.

 

때문에 model이 관리하는 데이터에 대해 controller에서 담당하지 못하도록 하려다 보니

Controller의 역할이 모호해졌고, 설계도 복잡해졌다.

 

하지만 레이어드 아키텍처와 MVC 패턴은 달랐고,

MVC패턴에서 Controller는 중간 다리 역할이므로 Model에 대해 알고 있어야 했다.

 

이를 고민하고 알게 된 덕분에 dto와 mapper를 이용한 model과 view 구분을 controller에서 담당하도록 했고,

그제야 model과 view의 역할을 명확하게 구분하는 중간 다리로 활용할 수 있었다.

참고 

 

DTO의 개념과 사용범위

DTO는 우테코 과정 중 정말 많이 들어봤고, 나름 사용도 많이 했지만 이상하게 바람직하게 사용하고 있다는 확신이 들지 않는 개념이다. DTO에 대한 내용은 항상 새롭게 알아가는데, 이러다간 DTO

hudi.blog

 

 

728x90

'Design Pattern' 카테고리의 다른 글

[Design Pattern] 전략 패턴(Strategy Pattern)(1)  (0) 2023.10.25