들어가며

  • 저번 포스트에서 Model의 추가를 통해 서블릿 코드 종속을 제거하고, ViewResolver와 MyView의 render()를 조금 손봄으로서 컨트롤러에선 View의 논리적 이름과 모델만을 반환하면 되도록 개선하였다.
  • 이번 포스트에서는 약간의 아이디어를 통해 더욱 편리하게 컨트롤러를 만들 수 있도록 한다.
  • 구현 코드의 결과는 여기에서 볼 수 있다.
 

GitHub - ForteEscape/MVC-Study

Contribute to ForteEscape/MVC-Study development by creating an account on GitHub.

github.com

 

 

 

아이디어

  • 지금 우리가 만든 이 FrontController와 프레임워크도 매우 탄탄한 구조를 가지고 있다. 여기서 조금의 아이디어를 통해 좋은 구조와 더불어 더 좋은 편리성까지 얻을 수 있도록 해보자.
  • 다음의 코드를 보자.
public class MemberListControllerV3 implements ControllerV3 {

    private static final MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public ModelView process(Map<String, String> paramMap) {

        List<Member> members = memberRepository.findAll();

        ModelView modelView = new ModelView("members");
        modelView.getModel().put("members", members);

        return modelView;
    }
}
  • ModelView에 우리가 정의한 모델을 가져와 해당 모델에 데이터를 넣고 있다.
  • 근데 생각해보면 이 모델은 결국  FrontController에서 빼내진 뒤에 다시 render()에 들어가야 한다. 즉 FrontController에서 다시 가공되게 된다.
  • 그렇다면 다음과 같은 생각도 해봄직하다.
    • FrontController에서 Model을 만들어서 파라미터로 넘겨도 되지 않을까?
  • ModelView 객체 내부에 있는 모델을 결국 빼내서 사용해야하기 때문에 ModelView를 만드는 번거로운 과정을 거치지 않고, FrontController에서 모델을 만들어 보내준다면 결과적으로 컨트롤러는 View의 논리적 이름만을 반환해도 괜찮을 것이다.
  • 즉 기존에 하나로 묶여있던 ModelView를 분리시켜 Model은 FrontController에서 만들어 공급하고, View 논리적 이름만을 반환하도록 만들면 된다.

 

 

 

구현

  • 추가적인 컴포넌트들이 없기 때문에 컨트롤러만 새로 구현하면 된다.
public interface ControllerV4 {

    String process(Map<String, String> paramMap, Map<String, Object> model);
}
  • 기존 ModelView에 있던 model을 매개변수로 받게 하였다. 그리고 뷰의 논리적 이름만을 반환하면 되기 때문에 String으로 반환 타입을 잡았다.
public class MemberFormControllerV4 implements ControllerV4 {
    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        return "new-form";
    }
}
public class MemberListControllerV4 implements ControllerV4 {

    private static final MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        List<Member> members = memberRepository.findAll();

        model.put("members", members);

        return "members";
    }
}
public class MemberSaveControllerV4 implements ControllerV4 {
    private static final MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    public String process(Map<String, String> paramMap, Map<String, Object> model) {
        String userName = paramMap.get("username");
        int age = Integer.parseInt(paramMap.get("age"));

        Member member = memberRepository.save(new Member(userName, age));
        model.put("member", member);

        return "save-result";
    }
}
  • 더 이상 ModelView를 생성하지 않아도 된다. 컨트롤러는 단지 뷰의 논리적 이름만을 반환한다. 모델은 FrontController에서 만들고 객체를 넘기기 때문에 컨트롤러에서 넣은 데이터들이 FrontController에도 정상적으로 반환될 것이다.

FrontController 구현

@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
    private final Map<String, ControllerV4> controllerMap = new HashMap<>();

    public FrontControllerServletV4(){
        controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
        controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
        controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestURI = request.getRequestURI();

        ControllerV4 controllerV4 = controllerMap.get(requestURI);
        if (controllerV4 == null){
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Map<String, String> paramMap = createParamMap(request);
        Map<String, Object> model = new HashMap<>();
        String viewName = controllerV4.process(paramMap, model);

        MyView view = viewResolver(viewName);
        view.render(model, request, response);
    }

    private static MyView viewResolver(String viewName) {
        return new MyView("/WEB-INF/views/" + viewName + ".jsp");
    }

    private static Map<String, String> createParamMap(HttpServletRequest request) {
        Map<String, String> paramMap = new HashMap<>();
        request.getParameterNames().asIterator()
                .forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));

        return paramMap;
    }
}
  • 코드를 보면 변경된 점이 매우 적다. 새로운 Map인 모델을 만들어 컨트롤러를 호출하는데 넣고, View의 논리적 이름을 반환받은 뒤, ViewResolver를 통해 MyView를 받도록 한 것 이외에는 변경된 점이 없다.
  • 이는 기존의 설계가 좋은 설계라는 것을 의미한다.

 

 

 

결과

  • 해당 아이디어를 적용함으로서 컨트롤러 개발자는 메서드 파라미터로 받은 모델에 데이터를 넣고, 뷰의 논리적 이름만을 그대로 반환하면 된다.
  • 기존의 ModelView를 만들어 내보낸 것보다 더 편리하게 변하였다. 이는 기존 컨트롤러가 해야 했던 일들을 프론트 컨트롤러에서 계속해서 가져가고 있기 떄문이다. 이렇게 프레임워크가 하는 일들이 많아질수록 이를 사용하는 유저는 점점 편리해진다.
  • 또한 기존의 설계가 매우 잘 되어 있었기 때문에 해당 아이디어를 적용하는데에도 그렇게 많은 변경점이 발생하지 않았다. 즉 이러한 편리함은 좋은 설계를 토대로 하여 달성될 수 있다고 할 수 있겠다.
  • 다음 포스트에서는 반환 타입이 다른 컨트롤러 등을 효과적으로 호출할 수 있도록 개선한다. 즉 확장성에 초점을 두고 개선해보도록 할 것이다.
복사했습니다!