Java MVC 구현

김정훈·2024년 6월 2일

Java

목록 보기
45/48
post-thumbnail

MVC 모델

Model

비즈니스 로직을 구현, service(기능)
회원이 서비스를 요청하고 입력한 데이터가 매개변수 👉 매개변수를 가지고 서비스를 응답.
DTO DAO Validator 등 다른 객체의 협력을 통해서 하나의 기능을 완성.

  • DTO(DATA Transfer Object) : 데이터 전달 목적의 객체
  • Validator : 데이터 검증
  • DAO(Data Acess Object) : 데이터 접근 객체(DB처리)
  • VO(Value Object)
    service 인터페이스, 클래스를 통해 구현

서비스설계할 때 기능에 대한 부분은 단일기능이어야한다.
하나의 클래스에 정의하는 기능이 여러개이면 한 객체가 담당하는 기능이 여러개이기 때문이다.

View

처리 결과 데이터를 가지고 보이는 부분
Template 인터페이스, 클래스를 통해 구현

Controller

사용자가 모델(서비스) 요청 👉 모델(서비스)요청 처리를 위한 뷰(응답)를 컨트롤러가 연결
템플릿 메서드 패턴을 통해 구현

  • 공통 : common()
  • 메뉴항목 :show()
  • 입력 : prompt()

Menu

public enum Menu {
    MAIN, // 메인 화면
    JOIN, // 회원 가입
    LOGIN, // 로그인
    MYPAGE, //마이페이지
}

Router

사용자가 입력한 메뉴 번호, 문구 👉 해당하는 컨트롤러로 연결

public interface Router {
    void change(Menu menu);
    void start(); //컨트롤러를 실행
}

MainRouter

MainControlle에서 사용자가 입력한 메뉴 번호, 문구 👉 해당하는 컨트롤러로 연결

change(Menu)메서드를 통해 매개변수로 받은 Menu상수를 기준으로 ControllerLocatorfind(Menu)를 통해 ControllerLocator가 생성한 해당하는 컨트롤러 객체를 연결해주고 run()를 통해 common(), show(), prompt()를 수행.

public class MainRouter implements Router {

	//싱글톤패턴
	private static Router instance;
    private MainRouter(){} //기본생성자
    public static Router getInstance(){ //다형성을위해 Router로
    	if(instance == null){
        	instance = new MainRouter(); //Router instance = new MainRouter();
    	}
        return instance;
   }
    
    

    @Override
    public void change(Menu menu) {
        ControllerLocator memlocator = MemberControllerLocator.getInstance();
        Controller controller = null; //다형성을 이용하여 모든 컨트롤러와 연결
        switch (menu){
            case JOIN: controller = memlocator.find(Menu.JOIN); break;
            case LOGIN: controller = memlocator.find(Menu.LOGIN); break;
            default: controller = new MainController();
        }
        controller.run(); //common(), show(), prompt()
    }

    @Override
    public void start() {
        while(true){
            change(Menu.MAIN); //첫 화면은 메인 컨트롤러 출력화면
        }

    }
}

ControllerLocator

각 컨트롤러를 찾고, 싱글톤패턴으로 컨트롤러의 객체를 생성하기 위한 인터페이스

public interface ControllerLocator {
    Controller find(Menu menu);
}

MemberControllerLocator

각 컨트롤러의 객체를 생성 or 찾기 를 수행하고, 해당 컨트롤러 객체는 MainRouter가 변경해준다.

Map controllers의 키값으로 MENU상수를 매개변수로 받아 각MENU에 해당하는 컨트롤러 객체를 가져옴.
만약 해당하는 컨트롤러 객체가 존재하지 않는다면, 각 MENU별로 해당하는 객체를 생성한다. 이후 생성된 객체를 Map controllers에 저장하고난뒤 반환해준다.
만약 해당하는 컨트롤러 객체가 존재한다면, 이미존재하는 해당 컨트롤러객체를 반환한다.

public class MemberControllerLocator implements ControllerLocator {

    private static ControllerLocator instance;

    private Map<Menu, Controller> controllers; 
    //각 컨트롤러 객체가 존재하지않으면 생성을하고 그 객체를 담을 Map

    private MemberControllerLocator(){
        controllers = new HashMap<>();
    }

    public static ControllerLocator getInstance(){ //ControllerLocator의 싱글톤패턴
        if(instance == null){
            instance = new MemberControllerLocator();
        }
        return instance;
    }

    @Override
    public Controller find(Menu menu) {
        Controller controller = controllers.get(menu); 
        //Map controllers의 키값으로 MENU상수를 매개변수로 받아 각 MENU에 해당하는 컨트롤러 객체를 가져옴.
        if (controller != null) { //해당하는 컨트롤러객체가 존재하면
            return controller; //그대로 컨트롤러객체를 반환.
        }
        switch(menu) { //해당하는 컨트롤러객체가 존재하지않는다면, 각 메뉴별로 해당하는 객체를 생성한다.
            case JOIN: controller = new JoinController(); break; //가입컨트롤러생성
            default: controller = new LoginController(); //로그인컨트롤러 생성
        }

        controllers.put(menu,controller); //Map controllers에 생성된 or 이미 존재하는 컨트롤러객체를 저장.

        return controller; //생성된, 이미 존재하는 컨트롤러 객체를 반환.
    }

}

Controller

사용자가 데이터를 제공하고 서비스를 요청하는 부분.

public interface Controller {
    void show(); // 서비스(Model)와 뷰를 연결
    void run(); //common show prompt를 실행
}

AbstractController

컨트롤러의 공통 기능을 구현

public abstract class AbstractController implements Controller {
	protected Scanner sc; // 하위 컨트롤러에서 공유해서 사용될 스캐너객체
    
    public AbstractController(){
    	sc = new Scanner(System.in)
    }
    /**
     * 상단 공통 출력 부분
     */
    public void common() {
       
    }

    /**
     * 입력 항목
     *  - 문자: q, exit, quit - 종료
     *  - 숫자: 메뉴 항목
     */
    public void prompt() {
    	System.out.print("메뉴 선택: ");
        String menu = sc.nextLine();
        if (menu.equals("q") || menu.equals("quit") || menu.equals("exit")) {
            System.out.println("종료 합니다.");
            System.exit(0); // 0 - 정상 종료, 1 - 비정상 종료
        }

        try {
            int m = Integer.parseInt(menu);
            change(m); // 메뉴 변경
        } catch (Exception e) {
            System.out.println("메뉴는 숫자로 입력하세요.");
        }
    }

    /**
     * 템플릿 메서드 패턴 : 특정 절차가 고정되어 있는 경우!
     *
     */
    @Override
    public final void run() {
        common();
        show();
        prompt();
    }
	
	//메뉴를 프롬포트서 입력받아 Menu menu의 값을 변경
    private void change(int menuNo) {
        Menu menu = null; //다형성을 위해 Menu
        switch(menuNo) {
            case 1: menu = Menu.JOIN; break; // 회원가입
            case 2: menu = Menu.LOGIN; break; // 로그인
            default: menu = Menu.MAIN; // 메인 메뉴
        }
        // 메뉴 컨트롤러 변경 처리 - Router
        MainRouter.getInstance().change(menu);
    }
    
    /**
     * 입력과 검증을 함께 진행
     * @param message : 항목 메세지
     * @param prdicate : 판별식
     */
    protected String promptWithValidation(String message, Predicate<String> prdicate){
    //하위 컨트롤러에서 공통으로 사용될수있는 메서드를 정의, 입력값을 검증할수있다
        String str = null;
        do{
            System.out.print(message);
            str = sc.nextLine();
        }while(!prdicate.test(str));

        return str;
    }
}

MainController

메인 컨트롤러

메뉴 상수의 값을 가지고 각 컨트롤러를 연결해줌.
또한 show()메서드를 오버라이딩하여, MainTpl을 출력해줌.

public class MainController extends AbstractController {
    @Override
    public void show() {
        Templates.getInstance().render(Menu.MAIN); //Show부분을 재정의하여, 해당하는 템플릿을 호출
    }
}

JoinController

회원가입 컨트롤러

show()를 통해 서비스처리결과를 템플릿으로 받는다.
prompt()를 통해 사용자가 데이터를 입력 👉 데이터를 하나의 엔티티(객체)로 생성하고 제공 👉 서비스를 요청한다.

public class JoinController extends AbstractController {
	//Controller인터페이스를 재정의하여 기술과 뷰를 연결 
    @Override
    public void show() {
        Templates.getInstance().render(Menu.JOIN); //Show부분을 재정의하여, 해당하는 템플릿을 호출
    }
     @Override
    public void prompt() {
       String userId = promptWithValidation("아이디(6자리 이상) : ", str -> str.length() >= 6);
       //AbstractController에 정의된 promptWithValidation()메서드를 사용하여 검증
       String userPw = promptWithValidation("비밀번호(8자리 이상) : ", s -> s.length() >= 8);
       String confirmPw = promptWithValidation("비밀번호 확인 : ", s ->
       {
           boolean match = s.equals(userPw);
           if(!match) {
               System.err.println("\n비밀번호가 일치 하지 않습니다.");
           }
           return match;
       });
       String userNm = promptWithValidation("회원명 : ", s -> !s.isBlank());

       RequestJoin form = RequestJoin.builder() //사용자가 입력한 데이터를 하나의 엔티티(객체)로 생성
               .userId(userId)
               .userPw(userPw)
               .confirmPw(confirmPw)
               .userNm(userNm)
               .build();

        Router router = MainRouter.getInstance();
        //컨트롤러 내부에서 다른컨트롤러로 변경하기위해 메인라우터를 생성or호출하여 컨트롤러를 변경함.

       try {
           //회원 가입 처리..
           Service service = MemberServiceLocator.getInstance().find(Menu.JOIN);
           service.process(form);

           //회원 가입 성공시 -> 로그인 화면
           router.change(Menu.LOGIN);
       }catch(RuntimeException e){
           //회원가입 실패시 -> 회원가입 화면으로 이동
           System.err.println(e.getMessage());
           router.change(Menu.JOIN); //컨트롤러 변경
       }
    }

}

LoginController

로그인 컨트롤러

show()를 통해 서비스처리결과를 템플릿으로 받는다.
prompt()를 통해 사용자가 데이터를 입력 👉 데이터를 하나의 엔티티(객체)로 생성하고 제공 👉 서비스를 요청한다.

public class LoginController extends AbstractController {
    @Override
    public void show() { //Show부분을 재정의하여, 해당하는 템플릿을 호출
        Templates.getInstance().render(Menu.LOGIN);
    }

    @Override
    public void prompt() {
        String userId = promptWithValidation("아이디 : ", s -> !s.isBlank());
        String userPw = promptWithValidation("비밀번호 : ", s -> !s.isBlank());

        RequestLogin form = RequestLogin.builder() //사용자가 입력한 데이터를 하나의 엔티티(객체)로 생성
                .userId(userId)
                .userPw(userPw)
                .build();

        Router router = MainRouter.getInstance();
        //컨트롤러 내부에서 다른컨트롤러로 변경하기위해 메인라우터를 생성or호출하여 컨트롤러를 변경함.
        
        //로그인 처리...
        try{
            Service service = MemberServiceLocator.getInstance().find(Menu.LOGIN);
            service.process(form);
            router.change(Menu.MAIN); // 로그인 성공시 -> 메인페이지
        }catch(RuntimeException e){
            System.err.println(e.getMessage());
            router.change(Menu.LOGIN); //로그인 실패시 -> 로그인페이지
        }

    }
}

Template

컨트롤러에서 사용자가 요청한 서비스의 처리 결과 데이터를 보여줌.

public interface Template {
    String getTpl(String tplName);
}

Templates(Locator)

각 Template의 객체를 생성 or 찾기 를 수행한다.

Map tpls의 키값으로 MENU상수를 매개변수로 받아 각MENU에 해당하는 템플릿 객체를 가져옴.
만약 해당하는 템플릿 객체가 존재하지 않는다면, 각 MENU별로 해당하는 템플릿 객체를 생성한다. 이후 생성된 객체를 Map tpls에 저장하고난뒤 반환해준다.
만약 해당하는 템플릿 객체가 존재한다면, 이미존재하는 해당 템플릿 객체를 반환한다.

public class Templates {
    private static Templates instance;
    private Map<Menu, Template> tpls;
    //각 템플릿 객체가 존재하지않으면 생성을하고 그 객체를 담을 Map
    private Templates() { // 생성자
        tpls = new HashMap<>();
    }
    public static Templates getInstance(){ //Templates의 싱글톤패턴
        if(instance == null){
            instance = new Templates();
        }
        return instance;
    }
    public void render(Menu menu){
        System.out.println(find(menu).getTpl());
    }

    public Template find(Menu menu){
        Template tpl = tpls.get(menu);
        //Map tpls의 키값으로 MENU상수를 매개변수로 받아 각 MENU에 해당하는 템플릿 객체를 가져옴.
        if(tpl != null){ //해당하는 탬플릿객체가 존재하면
            return tpl; /그대로 템플릿객체를 반환.
        }
        switch (menu){ //해당하는 템플릿객체가 존재하지않는다면, 각 메뉴별로 해당하는 템플릿객체를 생성한다.
            case JOIN: tpl = new JoinTpl(); break; //가입템플릿 생성
            case LOGIN: tpl = new LoginTpl(); break; //로그인템플릿 생성
            default: tpl = new MainTpl();
        }

        tpls.put(menu,tpl);
        return tpl;
    }

    public String line(){
        return "------------------------------------------\n";
    }

    public String doubleLine(){
        return "==========================================\n";
    }
}

MainTpl

public class MainTpl implements Template {
    @Override
    public String getTpl() {
        StringBuffer sb = new StringBuffer(2000);
        //문자열을 여러개 사용해서 결합하는것은 문자열을 만들떄 마다 문자열객체가 만들어진다.
        //버퍼를 사용하여 결합하여 사용해서 사용하는 것이 메모리 소비가 덜하다.
        sb.append("메뉴를 선택하세요. \n")
                .append("1. 회원가입\n")
                .append("2. 로그인\n")
                .append(Templates.getInstance().line());
        return sb.toString();
    }
}

JoinTpl

public class JoinTpl implements Template {
    @Override
    public String getTpl() {
        StringBuffer sb = new StringBuffer(1000);
        sb.append("회원가입\n");
        sb.append("다음 가입 항목을 입력하세요.\n");
        sb.append(Templates.getInstance().line());
        return sb.toString();
    }
}

LoginTpl

public class LoginTpl implements Template {
    @Override
    public String getTpl() {
        StringBuffer sb = new StringBuffer(1000);
        sb.append("로그인\n");
        sb.append("다음 로그인 항목을 입력하세요.\n");
        sb.append(Templates.getInstance().line());
        return sb.toString();
    }
}

Entity

사용자가 서비스를 받기위해 제공한 데이터를 담음.

RequestJoin

@Data
@Builder
public class RequestJoin {
    private String userId;
    private String userPw;
    private String confirmPw;
    private String userNm;
}

RequestJoin

@Data
@Builder
public class RequestLogin {
    private String userId;
    private String userPw;
}

ServiceLocator

각 도메인별로 서비스를 찾고 객체를 생성해줌.

public interface ServiceLocator {
    Service find(Menu menu);
}

AbstractServiceLocator

public abstract class AbstractServiceLocator implements ServiceLocator{
	Protected staitc ServiceLocator instance;
    protected Map<Menu, Service> services;

    protected AbstractServiceLocator(){
        services = new HashMap<>();
    }
    
}

MemberServiceLocator

public class MemberServiceLocator extends AbstractServiceLocator {

    public static ServiceLocator getInstance(){ //MemberServiceLocator의 싱글톤패턴
        if(instance == null){
            instance = new MemberServiceLocator();
        }
        return instance;
    }

    @Override
    public Service find(Menu menu) {
        Service service = services.get(menu);
        //Map services의 키값으로 MENU상수를 매개변수로 받아 각 MENU에 해당하는 서비스 객체를 가져옴.
        if(service !=  null){ //해당하는 서비스객체가 존재하면
            return service; //그대로 서비스객체를 반환.
        }
        switch (menu){ //해당하는 서비스객체가 존재하지않는다면, 각 메뉴별로 해당하는 서비스 객체를 생성한다.
            case JOIN: service = new JoinService(); break; //가입서비스 객체생성
            case LOGIN: service = new LoginService(); break; //로그인서비스 객체생성

        }
        return service; //생성된, 이미 존재하는 서비스 객체를 반환.
    }
}

Service

사용자가 입력한 데이터의 객체를 통해 요청한 서비스를 처리함.
다양한 형태의 자료형을 담기위해 제네릭을 사용.

public interface Service<T> { //다양한 형태의 자료형을 담기위해 제네릭을 사용.
    void process(T form); //
}

JoinService

public class JoinService implements Service<RequestJoin> {

    @Override
    public void process(RequestJoin form) {

    }
}

LoginService

public class LoginService implements Service<RequestLogin> {
    @Override
    public void process(RequestLogin form) {

    }
}
profile
안녕하세요!

0개의 댓글