비즈니스 로직을 구현, service(기능)
회원이 서비스를 요청하고 입력한 데이터가 매개변수 👉 매개변수를 가지고 서비스를 응답.
DTO DAO Validator 등 다른 객체의 협력을 통해서 하나의 기능을 완성.
service 인터페이스, 클래스를 통해 구현서비스설계할 때 기능에 대한 부분은 단일기능이어야한다.
하나의 클래스에 정의하는 기능이 여러개이면 한 객체가 담당하는 기능이 여러개이기 때문이다.
처리 결과 데이터를 가지고 보이는 부분
Template 인터페이스, 클래스를 통해 구현
사용자가 모델(서비스) 요청 👉 모델(서비스)요청 처리를 위한 뷰(응답)를 컨트롤러가 연결
템플릿 메서드 패턴을 통해 구현
common()show()prompt()public enum Menu {
MAIN, // 메인 화면
JOIN, // 회원 가입
LOGIN, // 로그인
MYPAGE, //마이페이지
}
사용자가 입력한 메뉴 번호, 문구 👉 해당하는 컨트롤러로 연결
public interface Router {
void change(Menu menu);
void start(); //컨트롤러를 실행
}
MainControlle에서 사용자가 입력한 메뉴 번호, 문구 👉 해당하는 컨트롤러로 연결
change(Menu)메서드를 통해 매개변수로 받은Menu상수를 기준으로ControllerLocator의find(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); //첫 화면은 메인 컨트롤러 출력화면
}
}
}
각 컨트롤러를 찾고, 싱글톤패턴으로 컨트롤러의 객체를 생성하기 위한 인터페이스
public interface ControllerLocator {
Controller find(Menu menu);
}
각 컨트롤러의 객체를 생성 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; //생성된, 이미 존재하는 컨트롤러 객체를 반환.
}
}
사용자가 데이터를 제공하고 서비스를 요청하는 부분.
public interface Controller {
void show(); // 서비스(Model)와 뷰를 연결
void run(); //common show prompt를 실행
}
컨트롤러의 공통 기능을 구현
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;
}
}
메인 컨트롤러
메뉴 상수의 값을 가지고 각 컨트롤러를 연결해줌.
또한show()메서드를 오버라이딩하여,MainTpl을 출력해줌.
public class MainController extends AbstractController {
@Override
public void show() {
Templates.getInstance().render(Menu.MAIN); //Show부분을 재정의하여, 해당하는 템플릿을 호출
}
}
회원가입 컨트롤러
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); //컨트롤러 변경
}
}
}
로그인 컨트롤러
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); //로그인 실패시 -> 로그인페이지
}
}
}

컨트롤러에서 사용자가 요청한 서비스의 처리 결과 데이터를 보여줌.
public interface Template {
String getTpl(String tplName);
}
각 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";
}
}
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();
}
}
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();
}
}
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();
}
}
사용자가 서비스를 받기위해 제공한 데이터를 담음.
@Data
@Builder
public class RequestJoin {
private String userId;
private String userPw;
private String confirmPw;
private String userNm;
}
@Data
@Builder
public class RequestLogin {
private String userId;
private String userPw;
}
각 도메인별로 서비스를 찾고 객체를 생성해줌.
public interface ServiceLocator {
Service find(Menu menu);
}
public abstract class AbstractServiceLocator implements ServiceLocator{
Protected staitc ServiceLocator instance;
protected Map<Menu, Service> services;
protected AbstractServiceLocator(){
services = new HashMap<>();
}
}
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; //생성된, 이미 존재하는 서비스 객체를 반환.
}
}
사용자가 입력한 데이터의 객체를 통해 요청한 서비스를 처리함.
다양한 형태의 자료형을 담기위해 제네릭을 사용.
public interface Service<T> { //다양한 형태의 자료형을 담기위해 제네릭을 사용.
void process(T form); //
}
public class JoinService implements Service<RequestJoin> {
@Override
public void process(RequestJoin form) {
}
}
public class LoginService implements Service<RequestLogin> {
@Override
public void process(RequestLogin form) {
}
}