2022.08.03/자바정리/추상클래스+인터페이스/스택자료구조/브래드크럼/

Jimin·2022년 8월 3일
1

비트캠프

목록 보기
15/60
post-thumbnail
  • board-app 프로젝트 수행
      1. 추상 클래스를 활용하여 인터페이스의 일부를 미리 구현하기
      1. 중첩 클래스 문법을 이용하여 클래스 사용 범위를 제한하기
      1. 스택 자료 구조를 활용하여 브래드크럼 메뉴 만들기
      1. 인터페이스를 이용하여 핸들러의 사용법을 규격화 하기
  • 인터페이스 사용법(eomcs-java-lang: com.eomcs.oop.ex09.*)(계속)

간단 알고리즘

  • 배열로 주어진 좌표를 점핑하는 수 구하기
// 출처: codefights.com
// 
// 장애물들의 x 좌표(양의 좌표)가 배열로 주어질 때,
// 0부터 오른쪽으로 동일한 거리를 점프하여 장애물들을 피한다면,
// 점프해야 할 최소 거리는 얼마인지 구하라!
// 
// 예) 
//   [5, 3, 6, 7, 9] ==> 4
// 
/*
You are given an array of integers representing coordinates of obstacles 
situated on a straight line.

Assume that you are jumping from the point with coordinate 0 to the right. 
You are allowed only to make jumps of the same length represented 
by some integer.

Find the minimal length of the jump enough to avoid all the obstacles.

Example

for [5, 3, 6, 7, 9] output should be 4

[input] array.integer inputArray
non-empty array of positive integers

[output] integer
the desired length
 */
//
// [시간 복잡도]
// - ?
//
public class Test08 {

  public static void main(String[] args) {
    System.out.println(avoidObstacles(new int[]{5,3,6,7,9,13,11}) == 4);
  }

  static int avoidObstacles(int[] inputArray) {
    // 이 메서드를 완성하시오!
    int ans = 1;
    // 점프하는 칸 수가 배열에 주어진 장애물의 좌표의 배수인가?
    // 배열에 주어진장애물들의 좌표 중에서 점프하는 칸 수의 배수가 있으면 안된다.
    // 즉, 나누어 떨어지면 안된다는 말이다.
    for(int i:inputArray) {
      if(i%ans==0) {
        ans++;
      }
    }
    return ans;
  }
}

인터페이스와 추상클래스

직접 구현

  • 클래스를 만드는 개발자가 인터페이스의 모든 메서드를 일일이 구현해야한다.
  • 필요하지 않은 메서드까지 모두 구현해야한다.

추상 클래스를 활용

  • AbstractClass
    서브 클래스에게 구현 메소드를 상속해주는 역할을 한다.
  • MyClass
    추상 클래스를 상속 받은 후, 관심있는, 내가 원하는 메서드만 오버라이딩한다.
    ⇒ 인터페이스 구현을 간소화할 수 있다.
public class Exam0120 {

  interface ProtocolA {
    void rule1();
    void rule2();
    void rule3();
    void rule4();
  }

  // 추상클래스에서 인터페이스의 규칙을 모두 미리 구현해 둔다.
  // 물론 최소 상태로 구현한다.
  abstract class AbstractProtocolA implements ProtocolA {
    @Override
    public void rule1() {}

    @Override
    public void rule2() {}

    @Override
    public void rule3() {}

    @Override
    public void rule4() {}
  }

  // 인터페이스를 준수하는 클래스를 만들어야 할 경우,
  // 직접 인터페이스를 구현하는 대신에
  // 다음과 같이 추상 클래스를 상속 받는다.
  // - 수퍼 클래스에서 인터페이스를 구현했다면,
  //   그 서브 클래스들도 인터페이스를 구현한 것이 된다.
  class ProtocolAImpl extends AbstractProtocolA {
    // 이 방식의 장점은 오버라이딩을 통해
    // 인터페이스 규칙 중 필요한 규칙만 구현할 수 있다.
    @Override
    public void rule2() {
      System.out.println("ProtocolAImpl.rule2()");
    }
  }

  void test() {
    ProtocolAImpl obj = new ProtocolAImpl();

    // 수퍼 클래스가 인터페이스를 구현했다면,
    // 그 서브 클래스는 자동으로 인터페이스를 구현한 것이 된다.
    // 증명!
    //
    ProtocolA a = obj;

    a.rule2();
  }

  public static void main(String[] args) {
    new Exam0120().test();
  }
}
  • 인터페이스ß 구현한 클래스 A가 있다고 쳤을 때,
    클래스 A를 수퍼 클래스로 하는 다른 클래스들 B, C, ..가 있다고 하면,
    이 B, C, ... 클래스들도 인터페이스 ß의 규칙을 따른다고 할 수 있다.
  • 즉, 인터페이스 데이터 타입 레퍼런스에 클래스 B, C, ...의 레퍼런스 주소값을 넣을 수 있다.
  • 추상 클래스에서 인터페이스의 규칙을 미리 구현해둔다.
    → 최소 상태로!

실전 예시

  • AbstractCar class
    • run()메서드는 추상 메서드인채로 남아있다.
    • 모든 서브 클래스가 공통으로 똑같은 기능으로 사용할 메서드인 on, off만 구현한다.
  • MyCar class
    • CarSpec 인터페이스에 맞추어 run 메서드를 오버라이딩 한다.
    • 즉, 필요한 메서드만 구현하면 된다.
    • 추상 클래스, 수퍼 클래스에서 미리 구현한 메서드는 서브 클래스에서 정의할 필요가 없기 때문에 편하다.

  • 객체는 상황에 따라 의미하는 바가 다르다. 문맥을 파악해야한다.




board-app project에 적용하기!

추상 클래스를 활용하여 인터페이스의 일부를 미리 구현하기

  • 추상클래스를 이용하여 List 규격의 메서드 중 서브 클래스에게 상속해 줄 일부 메서드를 미리 구현한다.
  • ObjectList와 LinkedList 클래스는 추상 클래스를 상속 받아서 List 규격을 간접적으로 구현한다.

1단계 - List 규격에 맞춰 AbstractList 구현하기

  • AbstractList 추상 클래스 생성
  • AbstractList 추상 클래스의 역할:
    • List 인터페이스를 구현한다.
    • 인터페이스의 메서드 중에서 서브 클래스에 공통으로 상속해줄 메서드를 일부 구현한다.
    • 물론 서브 클래스에 상속해 줄 필드가 있다면 이 클래스에서 미리 추가한다.
  • AbstractList 추상 클래스에 size() 메서드와 size 필드 구현
    • 목록에 저장된 항목의 개수를 저장하는 필드는 모든 서브 클래스가 가져야 할 필드이다.
    • 또한 이 필드는 서브 클래스에서 접근 가능하도록 개방한다. ⇒ protected
    • 즉, 서브 클래스가 사용할 수 있도록 물려주기 위해 만든 필드는 서브 클래스에서 직접 접근할 수 있게 해야 한다.
  • abstractClass에서는 정말 공통되는 것만 구현해놓고 다르게 기능하는 것들은 List 인터페이스에서는 규칙들만 구현한 것을 따른다.
  • List 인터페이스의 나머지 메서드는 추상 메서드인채로 그냥 둔다.
    → 왜? 서브 클래스마다 구현하는 방법이 다르기 때문이다.
  • AbstractList class
package com.bitcamp.util;

public abstract class AbstractList implements List{

  // 목록에 저장된 항목의 개수를 저장하는 필드는 모든 서브 클래스가 가져야 할 필드이다.
  // 또한 이 필드는 서브 클래스에서 접근 가능하도록 개방한다.
  // 즉 서브 클래스가 사용할 수 있도록 물려주기 위해 만든 필드는
  // 서브 클래스에서 직접 접근할 수 있게 해야 한다.
  //
  protected int size; // 같은 패키지에 소속된 클래스 + 서브 클래스는 직접 접근 가능!

  // 서브 클래스에게 상속해 줄 메서드를 미리 구현
  @Override
  public int size() {
    return size;
  }
}

2단계 - ObjectList와 LinkedList는 AbstractList를 상속한다.

  • ObjectList 클래스 변경
    • AbstractList가 구현하지 않은 List의 나머지 메서드를 구현한다.
    • size관련 코드는 이미 AbstractList가 구현해놨기 때문에 삭제한다.
public class ObjectList extends AbstractList {
...
  • LinkedList 클래스 변경
    • AbstractList가 구현하지 않은 List의 나머지 메서드를 구현한다.
    • size관련 코드는 이미 AbstractList가 구현해놨기 때문에 삭제한다.
public class LinkedList extends AbstractList {
...

board-app

중첩 클래스 문법을 이용하여 클래스 사용 범위를 제한하기

  • 특정 클래스 안에서만 사용될 클래스라면 그 클래스 내부에 정의하는 것이 유지보수에 좋다.
  • 클래스 안에 정의된 클래스를 중첩 클래스(nested class)라고 한다.
  • 특정 인스턴스에 종속되지 않는(인스턴스 사용X) 중첩 클래스라면 static nested class( 스태틱 중첩클래스)로 정의한다.
  • 패키지 클래스인 Node 클래스를 LinkedList의 스태틱 중첩 클래스로 전환하여 LinkedList를 다룰 때만 Node 클래스를 유지보수 할 수 있게 사용 범위를 제한한다.

1단계 - LinkedList가 사용하는 Node클래스를 중첩 클래스로 변경한다.

  • util.LinkedList 클래스 변경
    • Node 클래스를 중첩 클래스로 정의한다.
  • util.Node 클래스 삭제
  • LinkedList에서만 사용할 클래스라면, 이 클래스 안에 선언하는 것이 유지보수에 좋다!
  • Node 클래스는 LinkedList의 특정 인스턴스에 종속되지 않기 때문에(사용X) static nested class( 스태틱 중첩클래스)로 정의한다.
  • 또 Node 클래스가 크기도 작으므로!
  • Node 클래스는 어차피 LinkedList에서만 사용하므로 넣어버리고, private으로 설정한다.
  • LinkedList class
package com.bitcamp.util;

/**
 * Node를 이용해 값을 목록을 관리하는 일을 한다.
 * 
 * @author vivi
 *
 */
public class LinkedList extends AbstractList {

  private Node head; // 첫 노드의 주소를 저장
  private Node tail; // 마지막 노드의 주소를 저장


  @Override
  public void add(Object value) {
    Node node = new Node(value);

    size++; // 목록의 크기를 한 개 증가시킨다.

    if (tail == null) {
      head = tail = node; // 첫 노드를 등록한다.
      return;
    }


    tail.next = node; // 리스트 끝에 새 노드를 연결한다.
    node.prev = tail; // 새 노드가 현재의 끝 노드를 가리키게 한다. 

    tail = node; // 새 노드를 끝 노드로 만든다.
  }

  @Override
  public Object get(int index) {

    if (index < 0 || index >= size) {
      throw new ListException("인덱스의 범위를 초과했습니다!");
    }

    Node cursor = head;

    for (int i = 0; i < index; i++) {
      cursor = cursor.next;
    }

    return cursor.value;
  }

  @Override
  public Object remove(int index) {

    if (index < 0 || index >= size) {
      throw new ListException("인덱스의 범위를 초과했습니다!");
    }

    size--;

    Object deleted;

    if (head == tail) { // 마지막 남은 노드를 제거할 때
      deleted = head.value; // 노드를 삭제하기 전에 리턴할 수 있도록 값을 임시 보관한다.
      head.value = null; // 노드에 들어 있는 값 객체의 주소를 비운다.
      head = tail = null;
      return deleted; // 메서드를 종료할 때 호출자에게 삭제한 값을 리턴한다.
    }

    Node cursor = head;

    for (int i = 0; i < index; i++) {
      cursor = cursor.next;
    }

    if (cursor.prev != null) { // 맨 앞 노드가 아니라면
      cursor.prev.next = cursor.next; // 현재 노드의 다음 노드 주소를 이전 노드의 next 저장
    } else { // 맨 앞 노드라면
      head = cursor.next; // 삭제할 다음 노드를 시작 노드로 설정한다. 
      head.prev = null; // 시작 노드이기에 앞노드를 가리키지 않게 한다.
    }

    if (cursor.next != null) { // 마지막 노드가 아니라면
      cursor.next.prev = cursor.prev; // 현재 노드의 이전 노드 주소를 다음 노드의 prev 저장
    } else { // 마지막 노드라면 
      tail = cursor.prev; // 현재 커서의 이전 노드를 마지막 노드로 설정한다.
      tail.next = null; // 마지막 노드이기에 다음 노드를 가리키지 않게 한다.
    }

    deleted = cursor.value; // 노드를 삭제하기 전에 노드에 들어 있는 값을 임시 보관해 둔다.
    cursor.value = null;
    cursor.prev = null;
    cursor.next = null;

    return deleted; // 메서드를 리턴할 때 삭제된 값을 호출자에게 전달한다.
  }

  @Override
  public Object[] toArray() {
    // 값을 담을 배열을 준비
    Object[] arr = new Object[size];

    // 노드를 따라 가면서 값을 꺼내 배열에 담는다.
    Node cursor = head;
    for (int i = 0; i < size; i++) {
      arr[i] = cursor.value;
      cursor = cursor.next;
    }

    return arr;
  }

  // LinkedList에서만 사용할 클래스라면, 이 클래스 안에 선언하는 것이 유지보수에 좋다!
  // 클래스 안에 정의된 클래스를 중첩 클래스, (nested class)라고 한다.
  //
  // 특정 인스턴스에 종속되지 않는 중첩 클래스라면 static nested class( 스태틱 중첩클래스)로 정의한다.
  // LinkedList의 특정 인스턴스에 종속되지 않기 때문에 static nested class( 스태틱 중첩클래스)로 정의한다.
  // 크기도 작으므로!
  // 어차피 LinkedList에서만 사용하므로 넣어버리고, private으로 설정한다.
  //
  private static class Node {
    Object value;
    Node prev;
    Node next;

    public Node(Object v) {
      this.value = v;
    }
  }
}// LinkedList 끝

스택 자료구조를 활용하여 breadcrumb 메뉴 만들기

  • 스택 자료구조를 구현한다.
  • 스택(Stack) 자료 구조를 활용하여 데이터를 저장하고 꺼낼 클래스를 정의한다.

Stack 방식으로 데이터를 저장할 클래스 정의

1단계 - LinkedList를 상속 받아 Stack 클래스를 구현한다.

  • util.Stack 클래스 생성
    • LinkedList를 상속한다.
    • push()/pop() 메서드를 추가로 정의한다.
      - 비록, push() 나 pop() 메서드는 LinkedList의 메서드를 호출하는 껍데기에 불과하지만, Stack이라는 개념을 명확하게 표현할 수 있어서 이렇게 별도의 메서드로 구현하는 것이다.
  • Stack class
    • add()
      스택의 맨 마지막에 값을 추가한다.
    • pop()
      스택의 맨 마지막 값을 꺼낸다. 꺼낸 값은 스택에서 제거한다.
    • empty()
      스택이 비어 있는지 여부를 알려준다.
    • peek()
      스택의 저장된 맨 마지막 값을 리턴한다. 제거하지 않는다.
    • toString()
      스택에 저장된 객체를 순서대로 꺼내, 문자열로 저장한다.
      String 클래스의 toString() 메서드를 오버라이딩 함으로써, print계열의 메서드에 현재 Stack의 레퍼런스 변수를 넣기만 해도 오버라이딩한 내가 원하는 방식으로 출력되게 할 수 있다.
package com.bitcamp.util;

public class Stack extends LinkedList{
  public void push(Object value) {
    add(value); // 수퍼 클래스의 메서드를 호출하여push() 기능을 구현한다.
  }

  public Object pop() {
    // 수퍼 클래스의 메서드를 호출하여pop() 기능을 구현한다.
    return remove(size()-1);
  }

  public boolean empty() { 
    return size() == 0;
  }

  public Object peek() { 
    return get(size()-1);
  }

  @Override
  public String toString() {
    StringBuffer buf = new StringBuffer();
    for(int i=0;i<size();i++) { // 스택에 저장된 개수만큼 반복한다.
      if(buf.length() > 0) {
        // 이미 버퍼에 저장된 문자열이 있다면,
        buf.append(" > ");
      }
      buf.append(get(i));
    }
    return buf.toString();
  }
}

2단계 - 메뉴의 제목을 저장할 수 있도록 스택 객체를 준비한다.

  • board.App 클래스 변경
    • Stack 객체를 준비한다.
      • 여러 클래스에서 공통으로 사용할 수 있도록 static 멤버로 선언한다.
      • 여러 클래스에서 접근할 수 있도록 public으로 공개한다.
public class App {
  //breadCrumb 메뉴를 저장할 스택을 준비
  public static  Stack breadcrumbMenu = new Stack();

  public static void main(String[] args) {
		...
  }
}

3단계 - 메뉴에 진입할 때 그 메뉴의 이름을 스택에 저장한다.

  • board.App 클래스 변경

    • 애플리케이션을 실행할 때 "메인" 메뉴 이름을 스택에 추가(push())한다.
    • 메뉴의 제목을 출력할 때 스택에서 문자열을 꺼내(pop()) 출력한다.
    • breadcrumb 메뉴바 적용에 따라 기존 코드를 리팩토링한다.
      • 메뉴명을 저장할 배열 String 타입의 menus를 만든다.
      • 이 메뉴 배열 menus를 가지고 메뉴에 진입할 때마다 Stack에 진입하는 메뉴를 push한다.
        (인덱스 조건을 추가해서 조건에 맞을 때만 진입하도록 한다.)
      • 메뉴가 끝날 때 다시 Stack에서 pop()해서 메뉴에서 나왔음을 나타낸다.
  • handler.XxxHandler 클래스 변경

    • 핸들러를 실행할 때 해당 핸들러의 메뉴 이름을 스택에 등록한다. (push())
    • 핸들러의 서브 메뉴 실행을 마치면, 스택에 등록된 메뉴를 제거한다. (pop())
    • App과 마찬가지로 menus 배열을 만들어서 메뉴를 출력하도록 한다.
  • print계열 함수들은 자동으로 알아서 toString()을 가져와서 출력을 하는데, 이 toString()을 오버라이딩 하면,
    print안에 레퍼런스 변수만 넣고 .toString()을 붙이지 않아도,
    Stack class에서 출력방식을 바꾼 오버라이딩한대로, 자동으로 출력이 된다.

System.out.printf("%s\n", breadcrumbMenu);
  • App class
public class App {
  //breadCrumb 메뉴를 저장할 스택을 준비
  public static  Stack breadcrumbMenu = new Stack();

  public static void main(String[] args) {
    welcome();

    BoardHandler boardHandler = new BoardHandler();
    BoardHandler readingHandler = new BoardHandler();
    BoardHandler visitHandler = new BoardHandler();
    BoardHandler noticeHandler = new BoardHandler();
    BoardHandler diaryHandler = new BoardHandler();
    MemberHandler memberHandler = new MemberHandler();

    // "메인" 메뉴의 이름을 스택에 등록한다.
    breadcrumbMenu.push("메인");

    // 메뉴명을 저장할 배열을 준비한다.
    String [] menus = {"게시판", "독서록", "방명록", "공지사항", "일기장", "회원"};

    loop: while (true) {

      // 메인 메뉴 출력
      System.out.printf("%s\n", breadcrumbMenu);
      printMenus(menus);

      try {
        int mainMenuNo = Prompt.inputInt("메뉴를 선택하세요[1..6](0: 종료) ");

        if(mainMenuNo > 0 && mainMenuNo <= menus.length) {
          //메뉴에 진입할 때 breadCrumb메뉴바에 그 메뉴를 등록한다.
          breadcrumbMenu.push(menus[mainMenuNo-1]);
        } else if(mainMenuNo == 0){
          break loop;
        }else {
          System.out.println("메뉴 번호가 옳지 않습니다!");
          continue; // while문의 조건 검사로 보낸다.
        }

        switch (mainMenuNo) {
          case 1: // 게시판
            boardHandler.execute();
            break;
          case 2: // 독서록
            readingHandler.execute();
            break;
          case 3: // 방명록
            visitHandler.execute();
            break;
          case 4: // 공지사항
            noticeHandler.execute();
            break;
          case 5: // 일기장
            diaryHandler.execute();
            break;
          case 6: // 회원
            memberHandler.execute();
            break;
          default: 
        } // switch
        breadcrumbMenu.pop();
      } catch (Exception ex) {
        System.out.println("입력 값이 옳지 않습니다.");
      }

    } // while

    System.out.println("안녕히 가세요!");
    Prompt.close();
  } // main

  static void welcome() {
    System.out.println("[게시판 애플리케이션]");
    System.out.println();
    System.out.println("환영합니다!");
    System.out.println();
  }

  static void printMenus(String[] menus) {
    for(int i=0;i<menus.length; i++) {
      System.out.printf("  %d: %s\n", i + 1, menus[i]);
    }
    System.out.println();
  }
}
  • BoardHandler class
public class BoardHandler {
  // 게시글 목록을 관리할 객체 준비
  private BoardDao boardDao = new BoardDao();

  // 모든 인스턴스가 같은 서브 메뉴를 가지기 때문에
  // 메뉴명을 저장할 배열은 스태틱, 클래스 필드로 준비한다.
  private static String [] menus = {"목록", "상세보기", "등록", "삭제", "변경"};

  static void printMenus(String[] menus) {
    for(int i=0;i<menus.length; i++) {
      System.out.printf("  %d: %s\n", i + 1, menus[i]);
    }
    System.out.println();
  }

  public void execute() {
    while (true) {
      System.out.printf("%s:\n", App.breadcrumbMenu); // printf가 알아서 toString을 호출하므로 따로 안붙여줘도 ㄱㅊ
      printMenus(menus);

      try {
        int menuNo = Prompt.inputInt("메뉴를 선택하세요[1..5](0: 이전) ");

        if(menuNo > 0 && menuNo <= menus.length) {
          //메뉴에 진입할 때 breadCrumb메뉴바에 그 메뉴를 등록한다.
          App.breadcrumbMenu.push(menus[menuNo-1]);
        } else if(menuNo == 0){
          return; // 메인 메뉴로 돌아간다.
        }else {
          System.out.println("메뉴 번호가 옳지 않습니다!");
          continue; // while문의 조건 검사로 보낸다.
        }

        displayHeadline();

        // 서브 메뉴의 제목을 출력한다.
        System.out.printf("%s: \n",App.breadcrumbMenu);
        switch (menuNo) {
          case 1: 
            this.onList(); break;
          case 2: this.onDetail(); break;
          case 3: this.onInput(); break;
          case 4: this.onDelete(); break;
          case 5: this.onUpdate(); break;
          default: 
        }

        displayBlankLine();
        App.breadcrumbMenu.pop();
      } catch (Exception ex) {
        System.out.printf("예외 발생: %s\n", ex.getMessage());
      }
    } // 게시판 while
  }

참고 - 면접 질문

  1. before: 스택과 큐를 설명하시오!
    • 스택: LIFO, 프링글스 통
    • 큐: FIFO, 휴지곽
  2. after: 적용 예를 들어보시오!
    • 스택 → 웹 페이지 방문 기록, breadcrumb
    • 큐 → 예약 처리 (먼저 예약한 사람 먼저 처리)



인터페이스를 이용하여 핸들러의 사용법을 규격화하기

  • 핸들러의 사용법을 인터페이스로 규격화 시킨다.
  • 기존의 핸들러를 인터페이스 규칙에 따라 구현한다.
  • App 클래스는 핸들러 규칙에 따라 사용한다.
    • 인터페이스의 레퍼런스 배열을 이용하여 여러 개의 핸들러 객체를 관리한다.
    • 인터페이스 규칙에 따라 핸들러를 실행한다.

1단계 - 핸들러의 사용법을 인터페이스로 규격화 한다.

  • handler.Handler interface 생성
    • 핸들러 사용규칙을 정의한다.
  • Handler Interface
// 사용자 요청을 다룰 객체의 사용법을 정의한다.
// 
public interface Handler {
  void execute();
}

2단계 - 핸들러 사용규칙에 따라 BoardHandler와 MemberHandler를 구현한다.

  • board.handler.BoardHandler 클래스 변경
    • Handler 인터페이스를 구현한다.
  • board.handler.MemberHandler 클래스 변경
    • Handler 인터페이스를 구현한다.
  • BoardHandler class
public class BoardHandler implements Handler{
...
}
  • MemberHandler class
public class MemberHandler implements Handler{
...
}

3단계 - 핸들러 규칙에 따라 핸들러 객체를 실행한다.

  • board.App 클래스 변경

    • Handler 객체를 레퍼런스 배열로 관리한다.
    • 핸들러를 실행할 때 규칙에 따라 메서드를 호출한다.
    • switch문 제거!
  • Handler 배열 만들기

    Handler[] handlers = new Handler[] { //개수 정하지 않고 배열선언한다.
        new BoardHandler(), // 게시판
        new BoardHandler(), // 독서록
        new BoardHandler(), // 방명록
        new BoardHandler(), // 공지사항
        new BoardHandler(), // 일기장
        new MemberHandler() // 회원
    };
  • Handler 배열을 이용하기 때문에 이제 switch문을 삭제하고 한 줄의 코드로 바꿀 수 있다!
    • switch문
switch (mainMenuNo) {
          case 1: // 게시판
            boardHandler.execute();
            break;
          case 2: // 독서록
            readingHandler.execute();
            break;
          case 3: // 방명록
            visitHandler.execute();
            break;
          case 4: // 공지사항
            noticeHandler.execute();
            break;
          case 5: // 일기장
            diaryHandler.execute();
            break;
          case 6: // 회원
            memberHandler.execute();
            break;
          default: 
        } // switch

// 메뉴 번호로 Handler 레퍼런스에 들어있는 객체를 찾아 실행한다.
handlers[mainMenuNo-1].execute();
  • App class
public class App {
  //breadCrumb 메뉴를 저장할 스택을 준비
  public static  Stack breadcrumbMenu = new Stack();


  public static void main(String[] args) {
    welcome();

    // 핸들러를 담을 레퍼런스 배열을 준비한다.
    Handler[] handlers = new Handler[] { //개수 정하지 않고 배열선언한다.
        new BoardHandler(), // 게시판
        new BoardHandler(), // 독서록
        new BoardHandler(), // 방명록
        new BoardHandler(), // 공지사항
        new BoardHandler(), // 일기장
        new MemberHandler() // 회원
    };

    // "메인" 메뉴의 이름을 스택에 등록한다.
    breadcrumbMenu.push("메인");

    // 메뉴명을 저장할 배열을 준비한다.
    String [] menus = {"게시판", "독서록", "방명록", "공지사항", "일기장", "회원"};

    loop: while (true) {

      // 메인 메뉴 출력
      System.out.printf("%s\n", breadcrumbMenu);
      printMenus(menus);

      try {
        int mainMenuNo = Prompt.inputInt("메뉴를 선택하세요[1..6](0: 종료) ");

        if(mainMenuNo > 0 && mainMenuNo <= menus.length) {
          //메뉴에 진입할 때 breadCrumb메뉴바에 그 메뉴를 등록한다.
          breadcrumbMenu.push(menus[mainMenuNo-1]);
        } else if(mainMenuNo == 0){
          break loop;
        }else {
          System.out.println("메뉴 번호가 옳지 않습니다!");
          continue; // while문의 조건 검사로 보낸다.
        }

        // 메뉴 번호로 Handler 레퍼런스에 들어있는 객체를 찾아 실행한다.
        handlers[mainMenuNo-1].execute();

        breadcrumbMenu.pop();
      } catch (Exception ex) {
        System.out.println("입력 값이 옳지 않습니다.");
      }

    } // while

    System.out.println("안녕히 가세요!");
    Prompt.close();
  } // main

  static void welcome() {
    System.out.println("[게시판 애플리케이션]");
    System.out.println();
    System.out.println("환영합니다!");
    System.out.println();
  }

  static void printMenus(String[] menus) {
    for(int i=0;i<menus.length; i++) {
      System.out.printf("  %d: %s\n", i + 1, menus[i]);
    }
    System.out.println();
  }
}
profile
https://github.com/Dingadung

0개의 댓글