diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..5a212e20e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,44 @@ +# 기능 구현 체크리스트 + +## 구현 기능 + +### 입력 부분 + +- [ ] 기능 선택 + - [ ] 1, 2, 3, Q 아닌 입력 값은 예외 처리 + +### 예외 처리 + +- [ ] 예외 처리 시 IllegalArgumentException를 발생 +- [ ] [ERROR]로 시작하는 에러 메시지 출력 +- [ ] 에러 메시지 출력 후 해당 부분부터 재입력 받기 + +### 출력 부분 + +- [ ] 기능 선택 출력 +- [ ] 페어 매칭 출력 +- [ ] 페어 매칭 결과 출력 + +### 페어 정보 + +- [ ] 크루 목록이 담긴 파일 리스트에 저장 +- [ ] 미션 목록 리스트 저장 +- [ ] 페어 정보 관리 + +### 페어 매칭 + +- [ ] 크루 목록 셔플 +- [ ] 순서대로 두명씩 페어 매칭 + - [ ] 총 인원이 홀수인 경우 마지막 페어에 포함 +- [ ] 같은 레벨에서 페어 만난 경험이 있는 경우 셔플 후 재매칭 +- [ ] 3회 시도까지 매칭이 되지 않거나 매칭을 할 수 있는 경우의 수가 없으면 에러 메시지를 출력 + +### 페어 조회 + +- [ ] 매칭 이력 없은 경우 에러 메시지 출력 +- [ ] 매칭 이력이 있는 경우 목록 출력 + +### 페어 초기화 + +- [ ] 페어 목록 초기화 + - [ ] 초기화 메시지 출력 diff --git a/src/main/java/pairmatching/Application.java b/src/main/java/pairmatching/Application.java index 6f56e741c..4cf2ca8bc 100644 --- a/src/main/java/pairmatching/Application.java +++ b/src/main/java/pairmatching/Application.java @@ -1,7 +1,11 @@ package pairmatching; +import pairmatching.controller.MatchingController; + public class Application { public static void main(String[] args) { // TODO 구현 진행 + MatchingController matchingController = new MatchingController(); + matchingController.run(); } } diff --git a/src/main/java/pairmatching/controller/MatchingController.java b/src/main/java/pairmatching/controller/MatchingController.java new file mode 100644 index 000000000..9c72c7cc4 --- /dev/null +++ b/src/main/java/pairmatching/controller/MatchingController.java @@ -0,0 +1,106 @@ +package pairmatching.controller; + +import java.util.List; +import pairmatching.domain.MatchingConditions; +import pairmatching.domain.constant.OptionCommand; +import pairmatching.domain.Pairs; +import pairmatching.domain.constant.RematchingCommand; +import pairmatching.service.MatchingService; +import pairmatching.view.InputView; +import pairmatching.view.OutputView; + +public class MatchingController { + + private final InputView inputView; + private final OutputView outputView; + private final MatchingService matchingService; + + public MatchingController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + this.matchingService = new MatchingService(); + } + + public void run() { + start(); + } + + private void start() { + while (true) { + String option = readOption(); + if (option.equals(OptionCommand.MATCHING.getCommand())) { + match(); + } + if (option.equals(OptionCommand.CHECK.getCommand())) { + check(); + } + if (option.equals(OptionCommand.RESET.getCommand())) { + reset(); + } + if (option.equals(OptionCommand.QUIT.getCommand())) { + break; + } + } + } + + private String readOption() { + outputView.printSelectOption(); + while (true) { + try { + return inputView.readOption(); + } catch (IllegalArgumentException e) { + outputView.printException(e); + } + } + } + + private void match() { + MatchingConditions matchingConditions; + while (true) { + matchingConditions = readConditions(); + if (matchingService.hasConditions(matchingConditions)) { + break; + } + outputView.printRematching(); + RematchingCommand rematching = RematchingCommand.from(inputView.readRematching()); + if (rematching == RematchingCommand.REMATCHING) { + break; + } + } + Pairs pairs = matchingService.matchPair(matchingConditions); + outputView.printPairs(pairs); + } + + private MatchingConditions readConditions() { + outputView.printSelectCondition(); + while (true) { + try { + List conditions = inputView.readConditions(); + return new MatchingConditions(conditions); + } catch (IllegalArgumentException e) { + outputView.printException(e); + } + } + } + + private void check() { + Pairs pairs = checkPairs(); + outputView.printPairs(pairs); + } + + private Pairs checkPairs() { + while (true) { + try { + MatchingConditions matchingConditions = readConditions(); + return matchingService.findPairs(matchingConditions); + } catch (IllegalArgumentException e) { + outputView.printException(e); + } + } + } + + private void reset() { + matchingService.resetData(); + outputView.printReset(); + } +} diff --git a/src/main/java/pairmatching/domain/Crew.java b/src/main/java/pairmatching/domain/Crew.java new file mode 100644 index 000000000..f713e2f06 --- /dev/null +++ b/src/main/java/pairmatching/domain/Crew.java @@ -0,0 +1,14 @@ +package pairmatching.domain; + +public class Crew { + + private final String name; + + public Crew(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/MatchingConditions.java b/src/main/java/pairmatching/domain/MatchingConditions.java new file mode 100644 index 000000000..617bceb31 --- /dev/null +++ b/src/main/java/pairmatching/domain/MatchingConditions.java @@ -0,0 +1,59 @@ +package pairmatching.domain; + +import java.util.List; +import java.util.Objects; +import pairmatching.domain.constant.Course; +import pairmatching.domain.constant.Level; +import pairmatching.domain.constant.Mission; +import pairmatching.exception.ExceptionMessage; + +public class MatchingConditions { + + private static final int INPUT_SIZE = 3; + private final Course course; + private final Level level; + private final Mission mission; + + public MatchingConditions(List conditions) { + validateSize(conditions); + this.course = Course.from(conditions.get(0)); + this.level = Level.from(conditions.get(1)); + this.mission = Mission.from(conditions.get(2)); + } + + private void validateSize(List conditions) { + if (conditions.size() != INPUT_SIZE) { + throw new IllegalArgumentException( + ExceptionMessage.INVALID_MATCHING_CONDITIONS.getMessage()); + } + } + + public Course getCourse() { + return course; + } + + public Level getLevel() { + return level; + } + + public Mission getMission() { + return mission; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MatchingConditions)) { + return false; + } + MatchingConditions conditions = (MatchingConditions) o; + return course == conditions.course && level == conditions.level && mission == conditions.mission; + } + + @Override + public int hashCode() { + return Objects.hash(course, level, mission); + } +} diff --git a/src/main/java/pairmatching/domain/MatchingRepository.java b/src/main/java/pairmatching/domain/MatchingRepository.java new file mode 100644 index 000000000..69cd359fa --- /dev/null +++ b/src/main/java/pairmatching/domain/MatchingRepository.java @@ -0,0 +1,68 @@ +package pairmatching.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import pairmatching.exception.ExceptionMessage; + +public class MatchingRepository { + + private List matchingRepository; + + public MatchingRepository() { + this.matchingRepository = new ArrayList<>(); + } + + public void add(Pairs pairs) { + matchingRepository.add(pairs); + } + + public boolean hasPairs(MatchingConditions conditions, Pairs makePairs) { + List sameLevelPairs = matchingRepository.stream() + .filter(pairs -> pairs.getMatchingConditions().getLevel() == conditions.getLevel()) + .collect(Collectors.toList()); + + for (Pairs sameLevel : sameLevelPairs) { + if (checkPair(makePairs, sameLevel)) { + return true; + } + } + return false; + } + + private boolean checkPair(Pairs makePairs, Pairs sameLevel) { + for (Pair pair : sameLevel.getPairs()) { + if (hasPair(makePairs, pair)) { + return true; + } + } + return false; + } + + private boolean hasPair(Pairs makePairs, Pair pair) { + for (Pair makePair : makePairs.getPairs()) { + boolean result = pair.isCrews(makePair); + if (result) { + return true; + } + } + return false; + } + + public Pairs search(MatchingConditions conditions) { + return matchingRepository.stream() + .filter(pairs -> pairs.getMatchingConditions().equals(conditions)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + ExceptionMessage.NON_EXISTENT_HISTORY.getMessage())); + } + + public boolean hasConditionsPair(MatchingConditions conditions) { + return matchingRepository.stream() + .noneMatch(pairs -> pairs.getMatchingConditions().equals(conditions)); + } + + public void reset() { + matchingRepository = new ArrayList<>(); + } +} diff --git a/src/main/java/pairmatching/domain/Pair.java b/src/main/java/pairmatching/domain/Pair.java new file mode 100644 index 000000000..0e769b29a --- /dev/null +++ b/src/main/java/pairmatching/domain/Pair.java @@ -0,0 +1,29 @@ +package pairmatching.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Pair { + + private static final int DUPLICATION_NUMBER = 1; + private final List crews; + + public Pair() { + this.crews = new ArrayList<>(); + } + + public void add(Crew crew) { + crews.add(crew); + } + + public boolean isCrews(Pair pair) { + return pair.getCrews().stream() + .filter(this.crews::contains) + .count() > DUPLICATION_NUMBER; + } + + public List getCrews() { + return Collections.unmodifiableList(crews); + } +} diff --git a/src/main/java/pairmatching/domain/Pairs.java b/src/main/java/pairmatching/domain/Pairs.java new file mode 100644 index 000000000..a4c537a0d --- /dev/null +++ b/src/main/java/pairmatching/domain/Pairs.java @@ -0,0 +1,28 @@ +package pairmatching.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Pairs { + + private final MatchingConditions matchingConditions; + private final List pairs; + + public Pairs(MatchingConditions matchingConditions) { + this.matchingConditions = matchingConditions; + this.pairs = new ArrayList<>(); + } + + public void add(Pair pair) { + pairs.add(pair); + } + + public MatchingConditions getMatchingConditions() { + return matchingConditions; + } + + public List getPairs() { + return Collections.unmodifiableList(pairs); + } +} diff --git a/src/main/java/pairmatching/domain/constant/Course.java b/src/main/java/pairmatching/domain/constant/Course.java new file mode 100644 index 000000000..6e75a8b54 --- /dev/null +++ b/src/main/java/pairmatching/domain/constant/Course.java @@ -0,0 +1,28 @@ +package pairmatching.domain.constant; + +import java.util.Arrays; +import pairmatching.exception.ExceptionMessage; + +public enum Course { + BACKEND("백엔드"), + FRONTEND("프론트엔드"); + + private String name; + + Course(String name) { + this.name = name; + } + + public static Course from(String input) { + return Arrays.stream(Course.values()) + .filter(course -> course.name.equals(input)) + .findAny() + .orElseThrow( + () -> new IllegalArgumentException( + ExceptionMessage.INVALID_MATCHING_CONDITIONS.getMessage())); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/pairmatching/domain/constant/Level.java b/src/main/java/pairmatching/domain/constant/Level.java new file mode 100644 index 000000000..fe9fa4906 --- /dev/null +++ b/src/main/java/pairmatching/domain/constant/Level.java @@ -0,0 +1,26 @@ +package pairmatching.domain.constant; + +import java.util.Arrays; +import pairmatching.exception.ExceptionMessage; + +public enum Level { + LEVEL1("레벨1"), + LEVEL2("레벨2"), + LEVEL3("레벨3"), + LEVEL4("레벨4"), + LEVEL5("레벨5"); + + private String name; + + Level(String name) { + this.name = name; + } + + public static Level from(String input) { + return Arrays.stream(Level.values()) + .filter(level -> level.name.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + ExceptionMessage.INVALID_MATCHING_CONDITIONS.getMessage())); + } +} diff --git a/src/main/java/pairmatching/domain/constant/Mission.java b/src/main/java/pairmatching/domain/constant/Mission.java new file mode 100644 index 000000000..30593efc5 --- /dev/null +++ b/src/main/java/pairmatching/domain/constant/Mission.java @@ -0,0 +1,37 @@ +package pairmatching.domain.constant; + +import java.util.Arrays; +import pairmatching.exception.ExceptionMessage; + +public enum Mission { + RACING_CAR("자동차경주", Level.LEVEL1), + LOTTO("로또", Level.LEVEL1), + BASEBALL_GAME("숫자야구게임", Level.LEVEL1), + + SHOPPING_BASKET("장바구니", Level.LEVEL2), + PAYMENT("결제", Level.LEVEL2), + SUBWAY_MAP("지하철노선도", Level.LEVEL2), + + PERFORMANCE_IMPROVEMENT("성능개선", Level.LEVEL4), + DEPLOYMENT("배포", Level.LEVEL4); + + private final String name; + private final Level level; + + Mission(String name, Level level) { + this.name = name; + this.level = level; + } + + public static Mission from(String input) { + return Arrays.stream(Mission.values()) + .filter(mission -> mission.name.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + ExceptionMessage.INVALID_MATCHING_CONDITIONS.getMessage())); + } + + public Level getLevel() { + return level; + } +} diff --git a/src/main/java/pairmatching/domain/constant/OptionCommand.java b/src/main/java/pairmatching/domain/constant/OptionCommand.java new file mode 100644 index 000000000..2d01aa358 --- /dev/null +++ b/src/main/java/pairmatching/domain/constant/OptionCommand.java @@ -0,0 +1,29 @@ +package pairmatching.domain.constant; + +import java.util.Arrays; +import pairmatching.exception.ExceptionMessage; + +public enum OptionCommand { + MATCHING("1"), + CHECK("2"), + RESET("3"), + QUIT("Q"); + + private final String command; + + OptionCommand(String command) { + this.command = command; + } + + public static OptionCommand from(String input) { + return Arrays.stream(OptionCommand.values()) + .filter(optionCommand -> optionCommand.command.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + ExceptionMessage.INVALID_COMMAND.getMessage())); + } + + public String getCommand() { + return command; + } +} diff --git a/src/main/java/pairmatching/domain/constant/RematchingCommand.java b/src/main/java/pairmatching/domain/constant/RematchingCommand.java new file mode 100644 index 000000000..0929c9935 --- /dev/null +++ b/src/main/java/pairmatching/domain/constant/RematchingCommand.java @@ -0,0 +1,27 @@ +package pairmatching.domain.constant; + +import java.util.Arrays; +import pairmatching.exception.ExceptionMessage; + +public enum RematchingCommand { + REMATCHING("네"), + QUIT("아니오"); + + private final String command; + + RematchingCommand(String command) { + this.command = command; + } + + public static RematchingCommand from(String input) { + return Arrays.stream(RematchingCommand.values()) + .filter(rematchingCommand -> rematchingCommand.command.equals(input)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + ExceptionMessage.INVALID_REMATCHING_COMMAND.getMessage())); + } + + public String getCommand() { + return command; + } +} diff --git a/src/main/java/pairmatching/exception/ExceptionMessage.java b/src/main/java/pairmatching/exception/ExceptionMessage.java new file mode 100644 index 000000000..663bf81f2 --- /dev/null +++ b/src/main/java/pairmatching/exception/ExceptionMessage.java @@ -0,0 +1,22 @@ +package pairmatching.exception; + +public enum ExceptionMessage { + INVALID_COMMAND("잘못된 입력값 입니다. 다시 입력해주세요. " + + "(매칭 - 1, 조회 - 2, 초기화 - 3, 종료 - Q)"), + INVALID_MATCHING_CONDITIONS("잘못된 입력 양식입니다. 다시 입력해주세요. (과정, 레벨, 미션)"), + NON_EXISTENT_HISTORY("매칭 이력이 없습니다."), + FAIL_MATCHING("매칭에 실패했습니다."), + INVALID_REMATCHING_COMMAND("잘못된 입력 양식입니다. 다시 입력해주세요. (네, 아니오)"), + PARSER_ERROR("파일을 읽을 수 없습니다."); + + private static final String ERROR_MESSAGE = "[ERROR] "; + private final String message; + + ExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return ERROR_MESSAGE + message; + } +} diff --git a/src/main/java/pairmatching/service/MatchingService.java b/src/main/java/pairmatching/service/MatchingService.java new file mode 100644 index 000000000..a4e1d0563 --- /dev/null +++ b/src/main/java/pairmatching/service/MatchingService.java @@ -0,0 +1,94 @@ +package pairmatching.service; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.ArrayList; +import java.util.List; +import pairmatching.domain.Crew; +import pairmatching.domain.MatchingConditions; +import pairmatching.domain.MatchingRepository; +import pairmatching.domain.Pair; +import pairmatching.domain.Pairs; +import pairmatching.exception.ExceptionMessage; +import pairmatching.util.MDParser; + +public class MatchingService { + + private static final int NUMBER_OF_LOOPS = 3; + private final MatchingRepository matchingRepository; + + public MatchingService() { + this.matchingRepository = new MatchingRepository(); + } + + public Pairs matchPair(MatchingConditions matchingConditions) { + Pairs pairs; + List crewNames = getCrews(matchingConditions); + pairs = getPairs(matchingConditions, crewNames); + return pairs; + } + + private Pairs getPairs(MatchingConditions matchingConditions, List crewNames) { + Pairs pairs; + int count = 0; + while (true) { + checkNumberOfLoops(count); + List crews = mixRandomly(crewNames); + pairs = makePairs(crews, matchingConditions); + if (matchingRepository.hasPairs(matchingConditions, pairs)) { + count++; + continue; + } + matchingRepository.add(pairs); + break; + } + return pairs; + } + + private void checkNumberOfLoops(int count) { + if (count == NUMBER_OF_LOOPS) { + throw new IllegalArgumentException(ExceptionMessage.FAIL_MATCHING.getMessage()); + } + } + + private List getCrews(MatchingConditions matchingConditions) { + MDParser mdParser = new MDParser(); + return mdParser.parserCrewData(matchingConditions.getCourse()); + } + + private List mixRandomly(List crewNames) { + List shuffle = Randoms.shuffle(crewNames); + List crews = new ArrayList<>(); + for (String name : shuffle) { + crews.add(new Crew(name)); + } + return crews; + } + + private Pairs makePairs(List crews, MatchingConditions matchingConditions) { + Pairs pairs = new Pairs(matchingConditions); + for (int i = 0; i < crews.size(); i += 2) { + Pair pair = new Pair(); + pair.add(crews.get(i)); + pair.add(crews.get(i + 1)); + if (crews.size() % 2 == 1 && i == crews.size() - 3) { + pair.add(crews.get(i + 2)); + pairs.add(pair); + break; + } + pairs.add(pair); + } + return pairs; + } + + public Pairs findPairs(MatchingConditions matchingConditions) { + return matchingRepository.search(matchingConditions); + } + + public void resetData() { + matchingRepository.reset(); + } + + public boolean hasConditions(MatchingConditions matchingConditions) { + return matchingRepository.hasConditionsPair(matchingConditions); + } +} diff --git a/src/main/java/pairmatching/util/MDParser.java b/src/main/java/pairmatching/util/MDParser.java new file mode 100644 index 000000000..2fe726922 --- /dev/null +++ b/src/main/java/pairmatching/util/MDParser.java @@ -0,0 +1,44 @@ +package pairmatching.util; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import pairmatching.domain.constant.Course; +import pairmatching.exception.ExceptionMessage; + +public class MDParser { + + private static final String BACKEND_FILE_PATH = "src/main/resources/backend-crew.md"; + private static final String FRONTEND_FILE_PATH = "src/main/resources/frontend-crew.md"; + + public List parserCrewData(Course course) { + List crewNames = new ArrayList<>(); + try { + if (course == Course.BACKEND) { + crewNames = parseData(BACKEND_FILE_PATH); + } + if (course == Course.FRONTEND) { + crewNames = parseData(FRONTEND_FILE_PATH); + } + } catch (IOException e) { + System.out.println(ExceptionMessage.PARSER_ERROR.getMessage()); + } + return crewNames; + } + + private List parseData(String filePath) throws IOException { + List crewNames = new ArrayList<>(); + BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); + while (true) { + String name = bufferedReader.readLine(); + if (name == null) { + break; + } + crewNames.add(name); + } + bufferedReader.close(); + return crewNames; + } +} diff --git a/src/main/java/pairmatching/view/InputView.java b/src/main/java/pairmatching/view/InputView.java new file mode 100644 index 000000000..eb89728ae --- /dev/null +++ b/src/main/java/pairmatching/view/InputView.java @@ -0,0 +1,30 @@ +package pairmatching.view; + +import camp.nextstep.edu.missionutils.Console; +import java.util.Arrays; +import java.util.List; +import pairmatching.domain.constant.OptionCommand; +import pairmatching.domain.constant.RematchingCommand; + +public class InputView { + + private static final String DELIMITER = ", "; + + public String readOption() { + String input = input().trim(); + return OptionCommand.from(input).getCommand(); + } + + public List readConditions() { + return Arrays.asList(input().split(DELIMITER)); + } + + public String readRematching() { + String input = input().trim(); + return RematchingCommand.from(input).getCommand(); + } + + private String input() { + return Console.readLine(); + } +} diff --git a/src/main/java/pairmatching/view/OutputView.java b/src/main/java/pairmatching/view/OutputView.java new file mode 100644 index 000000000..eac603955 --- /dev/null +++ b/src/main/java/pairmatching/view/OutputView.java @@ -0,0 +1,42 @@ +package pairmatching.view; + +import static pairmatching.view.ViewMessage.*; + +import java.util.StringJoiner; +import pairmatching.domain.Crew; +import pairmatching.domain.Pair; +import pairmatching.domain.Pairs; + +public class OutputView { + + public void printException(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + public void printSelectOption() { + System.out.printf(SELECT_OPTION.getMessage()); + } + + public void printSelectCondition() { + System.out.printf(INFO.getMessage()); + } + + public void printPairs(Pairs pairs) { + System.out.printf(RESULT.getMessage()); + for (Pair pair : pairs.getPairs()) { + StringJoiner result = new StringJoiner(RESULT_DELIMITER.getMessage()); + for (Crew crew : pair.getCrews()) { + result.add(crew.getName()); + } + System.out.println(result); + } + } + + public void printReset() { + System.out.printf(RESET.getMessage()); + } + + public void printRematching() { + System.out.printf(REMATCHING.getMessage()); + } +} diff --git a/src/main/java/pairmatching/view/ViewMessage.java b/src/main/java/pairmatching/view/ViewMessage.java new file mode 100644 index 000000000..f16753a5a --- /dev/null +++ b/src/main/java/pairmatching/view/ViewMessage.java @@ -0,0 +1,30 @@ +package pairmatching.view; + +public enum ViewMessage { + SELECT_OPTION("%n기능을 선택하세요.%n1. 페어 매칭%n2. 페어 조회%n3. 페어 초기화%nQ. 종료%n"), + INFO("%n#############################################%n" + + "과정: 백엔드 | 프론트엔드%n" + + " - 레벨1: 자동차경주 | 로또 | 숫자야구게임%n" + + " - 레벨2: 장바구니 | 결제 | 지하철노선도%n" + + " - 레벨3: %n" + + " - 레벨4: 성능개선 | 배포%n" + + " - 레벨5: %n" + + "#############################################%n" + + "과정, 레벨, 미션을 선택하세요.%n" + + "ex) 백엔드, 레벨1, 자동차경주%n"), + REMATCHING("%n매칭 정보가 있습니다. 다시 매칭하시겠습니까?%n" + + "네 | 아니오%n"), + RESULT("%n페어 매칭 결과입니다.%n"), + RESULT_DELIMITER(" : "), + RESET("%n초기화 되었습니다.%n"); + + private final String message; + + ViewMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +}