퀴즈의 설계는 이런 식으로 해보려고 합니다 헌데 강사님이 말씀하신 것이 부모를 먼저 만드는 것이 아닌 자식을 먼저 만들고 그리고 거기서 중복적으로 사용되는 것들을 부모로 올려서 부모를 만들라고 하셔서 그렇게 진행 보겠습니다
우선 가장 먼저 ox퀴즈를 만들어보았습니다 만들어보니 중복되는 코드가 퀴즈 문제 , 퀴즈 정답 그리고 사용자의 대답 그리고 각 퀴즈를 설명해줄 수 있는 공간 등이 중복적으로 필요하다는 걸 알았습니다 그래서 그친구들을 부모 클래스로 옮겨서 만들었습니다
부모 Class Quiz
@Setter
public abstract class Quiz {
//퀴즈 문제 하위에서도 봐야하기에 protected
protected String title;
//퀴즈 정답 하위에서도 봐야하기에 protected
protected String answer;
//문자열로 유자가 생각하는 답
public abstract boolean checkAnswer(String userAnswer);
//Override 해줄 공간 quiz 설명 ox 인지 객관식인지 주관식 인지 설명해주는
public abstract String getDesc();
//getter을 안 쓰는 건 이거 하나 때문에 getter을 쓰면 다른 애들도 영향을 받기 때문에
public String getTitle(){
return this.title;
}
}
부모 클래스인 Quiz 에서 중요한 부분은 abstract(추상 메서드) 부분입니다 우선 클래스 전체에 abstract(추상 메소드) 가 걸려있기에 자식 클래스들에서 볼 수 있게 title와 answer을 protected로 지정해주고
checkAnswer (유저가 생각하는 답) getDesc(각 퀴즈를 설명해주는 곳) 등에 abstract(추상 메소드) 를 걸어주었습니다
Quiz를 상속하는 OXquiz
@Builder
public class OXquiz extends Quiz {
//문자열로 유자가 생각하는 답
@Override
public boolean checkAnswer(String userAnswer) {
return this.answer.equals(userAnswer);
}
@Override
public String getDesc(){
return "O or X";
}
}
그 결과 Quiz를 상속받은 OXquiz는 checkAnswer (유저가 생각하는 답) getDesc(각 퀴즈를 설명해주는 곳)를 @override 해주고 있습니다
Test
현재까지 작성된 코드가 정삭적으로 작동되는지 확인을 위해 Test를 해보았습니다
public class QuizTests {
@Test
public void test1(){
Quiz quiz = OXquiz.builder().build();
quiz.setTitle("세종대왕은 한국사람이다");
quiz.setAnswer("O");
System.out.println(quiz.getTitle());
System.out.println(quiz.getDesc());
}
}
⭐결과
> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
세종대왕은 한국사람이다
O or X
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
👏 정상적으로 작동되고 있네요 👏
객관식 Quiz <SubQuiz>
//객관식
@Setter
@Builder
public class SubQuiz extends Quiz{
//객관식 문제를 위한 배열
public String[] arr;
@Override
public String getDesc() {
//보기의 내용을 보여주기
return Arrays.toString(arr);
}
@Override
public boolean checkAnswer(String userAnswer) {
//유저가 정답을 배열의 번호로 말했을경우 super.checkAnswer 은 부모의 기능을 그대로 쓰기위한
boolean condition1 = super.checkAnswer(userAnswer);
//유저가 바로 정답을 말하면 true
if(condition1){
return true;
}
//유저 값이 배열에 없으면 그냥 나가게 하기위해 -1값이면
int userIdx = -1;
//배열을 돌면서 배열의 값과 i값을 비교
for (int i = 0; i < arr.length; i++) {
if(userAnswer.equals(arr[i])){
userIdx = i;
break;
}
}
//유저가 정답을 번호가아닌 정답자체를 말했을 경우
// userIdx +1 을 해준이유는 배열은 0번부터 시작하는데 유저의 값은 1부터 입력하기에
boolean condition2 = userIdx +1 == Integer.parseInt(answer);
//유저가 정답을 번호가 아닌 정답자체 즉 String 으로 말했을때
if(condition2){
return true;
}
//이도저도 아닐때 false
return false;
}
}
객관식 코드를 작성하면서 코드의 중요한 부분이 재정의 되었습니다 바로 checkAnswer입니다 이곳에서는 override를 해주고 있지만
Main checkAnswer 변경
//문자열로 유자가 생각하는 답
public boolean checkAnswer(String userAnswer) {
return this.answer.equals(userAnswer);
}
보시면 원래 checkAnswer 추상 메서드였습니다 하지만 abstract를 제거해주었습니다 그 이유는 유저가 정답을 입력하는 것 또한 중복되는 코드이기에 abstract를 제거해주고 객관식 같은 경우에서는 보시면 ox퀴즈와는 달리 정답을 입력받는 방식이 하나 추가되었습니다 그렇기에 override를 해주면서 기능을 재정의 해준 것입니다
Test
@Test
public void test2(){
SubQuiz subQuiz = SubQuiz.builder().build();
subQuiz.setTitle("가장 섹시한 근육은?");
subQuiz.setAnswer("1");
subQuiz.setArr(new String[]{"등","가슴","어깨","하체"});
System.out.println(subQuiz.getTitle());
System.out.println(subQuiz.getDesc());
}
⭐결과
> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
3월 15, 2022 7:57:46 오후 org.junit.platform.launcher.core.EngineDiscoveryOrchestrator lambda$logTestDescriptorExclusionReasons$7
정보: 0 containers and 1 tests were Method or class mismatch
가장 섹시한 근육은?
[등, 가슴, 어깨, 하체]
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
테스트해보니 정상적으로 작동하고있습니다
주관식 Quiz <Objquiz>
//주관식
public class Objquiz extends Quiz{
@Override
public String getDesc() {
return "이 문제는 주관식 문제입니다";
}
}
주관식은 ox 와 똑같은 모양입니다
이렇게 퀴즈의 모양은 얼추 잡은 것 같습니다 지금까지의 코드를 보니 우리는 이런 생각을 할 수 있습니다 우리는 퀴즈의 배열을 만들 수 있다는 이야기이고 그 퀴즈를 가지고 오기만 하면 됩니다
인터페이스
기존 퀴즈 로직들은 domain 안에 들어있고 새롭게 repository와 ui 패키지를 만들어주었습니다
그 후 repository안에 QuizRepository 이름에 Interface를 만들어주었습니다
첫 인터페이스를 만들었습니다 👏
이후 인터페이스에 관한 내용을 좀 더 설명해주셨습니다
인터페이스는 서로 마주 봐야 한다고 말씀해주셨습니다 그렇기에 인터페이스는 무조건 public이고 굳이 public를 선언 안 해주어도 상관없다고 합니다
이렇게 public를 선언해도 회색으로 표시됩니다
이제 인터페이스를 이해하려면 이 그림을 다시 한번 봐야 할 것 같습니다
이 상황에서 웨이터가 주방에게 주문을 합니다 그러면 주방에서 음식을 만드는데 만약 여기서 주방장이 아파서 다른 사람이 온다면? 다른 주방장이 와서 음식을 만들 겁니다 그 이야기는 웨이터는 주방장이 누구 건간에 주방장에게 메뉴를 전달하기만 하면 됩니다 이걸 로직으로 설명해보자면 위에서 만든 ui는 웨이터이고 인터페이스는 주방입니다
ui는 인터페이스만 바라보고 호출 <주문>하면 되는 겁니다 그 뒤에 어떤 로직이 있든 간에 모든 호출은 인터페이스를 바라보고 호출하는 게 정석이라는 것이지요 그렇게 생각하면 public를 이해하는 것도 쉬워집니다 왜냐 인터페이스는 서로 마주 봐야 하기 때문에 주방장과 웨이터는 마주 보고 일을 해야 하기 때문에!! 무조건 public으로 만들어지는 것입니다
자 이제 로직으로 넘어와서 인터페이스에 코드를 작성했습니다
public interface QuizRepository {
public Quiz getNextQuiz();
}
이 코드는 뒤에 다시 한번 이야기해보고 ui 쪽으로 이동해보겠습니다
UI
@AllArgsConstructor
public class QuizUI {
//UI는 인터페이스만 알고있으면 된다 !!
private QuizRepository quizRepository;
private Scanner scanner;
}
ui를 보시면 이제 중요한 이야기다 다시 한번 나옵니다 보시면 ui는 QuizRepository 즉 인터페이스만 알고 있으면 됩니다 예전에는 클래스를 바라보고 코딩을 했다면 이제는 그 뒤에 무엇이 있을지 모르지만 QuizRepository 타입이기만 하면 됩니다 이것이 지난번에 공부했던 의존성 주입의 진짜 활용이라고 하셔서 살짝 소름이 돋아버렸습니다
@AllArgsConstructor
public class QuizUI {
//UI는 인터페이스만 알고있으면 된다 !!
private QuizRepository repository;
private Scanner scanner;
public void play(){
Quiz quiz = repository.getNextQuiz();
//퀴즈 문제 title 를 return 하는 코드
System.out.println(quiz.getTitle());
//어떤 퀴즈인지 설명하는 코드
System.out.println(quiz.getDesc());
//유저에게 입력받기
String userAnswer = scanner.nextLine();
//퀴즈 체크하던 기능
boolean corrent= quiz.checkAnswer(userAnswer);
//퀴즈 정답이 오답일때
if(!corrent){
System.out.println("이걸 틀려~?");
return;
}
//재귀호출
play();
}
}
ui부분은 보시는 것처럼 기존에 만들었던 필요한 기능들만 불러와주면 끝이 납니다
이제 아까 만들었던 QuizRepository(퀴즈 저장소 역할)로 돌아가서 나머지 기능을 만들어 보겠습니다
QuizRepository
repository 패키지 안에 저희 팀은 영화를 주제로 한 퀴즈를 만들기로 해서 MovieQuizRepository Class를 만들어주었습니다
public class MovieQuizRepository implements QuizRepository{
//퀴즈 배열
private Quiz[] quizzes;
//생성자
public MovieQuizRepository(){
quizzes = new Quiz[1];
//ox퀴즈
OXquiz quiz1 = OXquiz.builder().build();
//문제
quiz1.setTitle("영화 아이언맨의 주인공은 토니 스타크이다");
//답
quiz1.setAnswer("O");
quizzes[0]=quiz1;
}
@Override
public Quiz getNextQuiz() {
return quizzes[0];
}
}
인터페이스인 QuizRepository와 연결해주고
퀴즈 배열을 만들고 거기에 기존에 만들었던 퀴즈 문제 로직 답 로직에 연결해주면서 MovieQuizRepository를 완성했습니다 이제 이러면 다른 팀들이 Class 이름만 Movie가 아닌 다른 주제들로 만들어서 보내주고 저희는 받아서 패키지에 넣지만 하면 여러 가지 퀴즈를 가지게 되는 겁니다... 정말.... 너무 신기했습니다
Main
public class Main {
public static void main(String[] args) {
Scanner scanner =new Scanner(System.in);
QuizRepository repository = new MovieQuizRepository();
QuizUI quizUI = new QuizUI(repository,scanner);
quizUI.play();
}
}
마지막으로 코딩을 확인해보기 위해 main을 만들어주고 확인해보았습니다 그 결과는!!!
이렇게 정상적으로 작동하고 있습니다 감격 또 감격 😭
다른 팀들이 만든 퀴즈들은 Main에서 new로 MovieQuizRepository 아닌 그 이름들의 파일만 불러주면 그 퀴즈들이 쭈르륵 ~ 나오게 되는 구조입니다 사실은 지금 new MovieQuizRepository 로 부르는 구조도 없이 다른 퀴즈를 부르는 방법도 있지만 아직은 이른 것 같다고 하셔서 여기까지 하고 나머지는 강사님이 하시는 것만 보았습니다 아직 배우지 못한 기능들이 있어서 그 기능들을 사용하니 진짜 점점 더 프로그램처럼 느껴졌습니다 지금 이 코드에서 가장 중요한 부분은 ui에서는 전혀 변하는 것이 없다는 것입니다 MovieQuizRepository가 아닌 다른 퀴즈를 불러와도 ui코드는 우리는 건들 필요가 없습니다 왜냐면 ui는 인터페이스만 보고 있기 때문이죠!!!
마무리
이렇게 8일 차 수업이 끝났습니다...... 8일 차..... 8일 차인데 이 속도...ㅎㅎ
강의 내용만 따라가는 것도 힘들게 느껴지지만 그래도 다 같이 으쌰 으쌰 하면서 하는 분위기여서 좀 더 힘을 내서 공부하고 있는 것 같습니다 강사님이 매번 해주시는 말씀은 당장 코딩을 못하는 건 상관없다 하지만 이해는 해야 한다라고 말씀해주셔서 최대한 이해해보려고 노력하고 있습니다 코딩도 잘해지면 좋을 텐데.....
내일은 또 어떤 엄청난 것들이 기다리고 있을지 기대가 전혀 안되고 무섭습니다
'개발자 성장 일지' 카테고리의 다른 글
2022.03.16 Interpace 활용 <3단 구현> (0) | 2022.03.16 |
---|---|
2022.03.16 복습 뱀 게임 (0) | 2022.03.16 |
2022.03.15 Interpace (0) | 2022.03.15 |
2022.03.15 데이터 위주의 상속 (0) | 2022.03.15 |
2022.03.14 상속을 이용해서 UI 만들어보기 (0) | 2022.03.14 |