✅네이버클라우드 캠프/개발일기

[네이버클라우드캠프] 39일차. 자바 CRUD 메뉴 구성과 디자인 패턴(Composite, Command, Observer)을 활용한 자료구조(Stack, Queue) 구현 예제

우동한그릇 2023. 6. 20. 17:22
반응형

 

 


 

* Stack, Queue 자료구조 구현

 

BreadCrumb (빵 부스러기) 를 이용해서 지나온 경로를 사용자에게 표시해준다.

이렇게 함으로써 현재 어떠한 메뉴를 이용하고 있는 지를 시각화해줄 수 있다.

 

예 : 메인/회원> 

 

* Stack 구동원리

 

Stack 은 LIFO (Last In First Out)이다.

스택에 값이 들어오게 되고 ,

후입선출로서 나중에 들어온 값이 먼저 빠져나가게 된다.

 

예) c:/>사용자>bitcamp>git>bitcamp-study  

 

나갈 때의 순서는 bitcamp-study > git > bitcamp 사용자 순서이다.

 

* Stack 구현 

자식 클래스는 부모 클래스의 메서드를 사용할 수 있다.

 

또한 부모 클래스가 구현한 객체는 모든 자식 클래스의 설계도를 바탕으로

객체를 생성하여 인스턴스 변수를 사용할 수 있다.

 

* 상속과 인스턴스 필드들

student class 는 Member 클래스를 상속받았기 때문에

Strundent를 설계도로 한 객체 s는

Member의 인스턴스 변수 정보를 갖고 있다.

 

* Stack 인스턴스

 

Stack class 는 LinkedList 클래스를 상속받았기 때문에 Stack 를 설계도로 한 객체 s는

LinkedList 의 인스턴스 변수 정보를 갖고 있다.

 

즉 Stack에는 push() 메소드가 add() 라는 메소드를 사용하기때문에 

LinkedList의 메서드를 사용하여 기능을 구현하여 head, tail, size 의 인스턴스 변수를 사용하게 된다.

 

이 인스턴스 변수는 LinkedList의 인스턴스 변수가 아니라 Stack 내부에서 정의된 인스턴스 변수이다.

* Stack, Queue 자료구조 구현

Breadcrumb 를 적용시키기 위해서 

MenuPrompt라는 클래스를 생성하고

inputMenu 메서드를 구성한다.

 

 

기존의 Prompt 클래스의 기능을 그대로 구현해주기 위해서,

반드시 기존 Prompt를 상속받아서 그 메서드 기능을 내부에서 구현해주어야한다.

이후에 기존 app.java, MemberHandler, BoardHandler 등 Prompt 를 import해서 메서드를 사용하는

클래스들을 수정해주어야한다. 

 

메뉴에 진입 할 때 (1,2,3)

prompt.appendBreadcrumb(this.title);


메뉴에서 빠져나올 때 (0)

prompt.removeBreadcrumb();

 

 MenuPrompt 클래스

package bitcamp.util;

public class MenuPrompt extends Prompt {

  private Stack breadcrumbs = new Stack();

  public void appendBreadcrumb(String title) {
    breadcrumbs.push(title);
  }

  public void removeBreadcrumb() {
    breadcrumbs.pop();
  }

  public String inputMenu() {
    StringBuilder titleBuilder = new StringBuilder();// 예) 메인/회원>
    for (int i = 0; i < breadcrumbs.size(); i++) {
      if (titleBuilder.length() > 0) {
        titleBuilder.append("/");
        // 기존에 경로가 있다면 구분해주기 위해서 "/"를 표시
      }
      titleBuilder.append(breadcrumbs.get(i));
    }
    titleBuilder.append("> ");
    return this.inputString(titleBuilder.toString());
  }



}

* Queue 구현과 적용

 

 

Queue 구현을 인터페이스 규칙에 따라서 생성하기

 

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Queue.html

 

Queue (Java SE 17 & JDK 17)

Type Parameters: E - the type of elements held in this queue All Superinterfaces: Collection , Iterable All Known Subinterfaces: BlockingDeque , BlockingQueue , Deque , TransferQueue All Known Implementing Classes: AbstractQueue, ArrayBlockingQueue, ArrayD

docs.oracle.com

 

  public void offer(Object value) {
    this.add(value);
  }

  public Object poll() {
    if (this.size() == 0) {
      return null;
    }
    return this.remove(0);
  }

 

 

* Stack 구현

Steck 구현을 인터페이스 규칙에 따라서 생성하기

 

 

public void push(Object value) {
    // 목록 맨 끝에 추가한다.
    // 따로 만들 필요가 없다.
    // 수퍼 클래스에 있는 메서드를 이용하여 기능을 구현한다.
    this.add(value); // 상속받은 메서드
                     // 서브 클래스에서 사용할 수 있는 수퍼 클래스의 메서드
  }

  public Object pop() {
    if (this.empty()) {
      return null;
    }
    return this.remove(this.size() - 1);
  }

  public Object peek() {
    if (this.empty()) {
      return null;
    }
    return this.get(this.size() - 1);
  }

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

 

Stack 클래스 설명

 

push 메서드:
push 메서드는 주어진 요소를 스택의 맨 끝에 추가
현재 구현은 add 메서드를 호출하여 요소를 추가하고 있습니다. 

상속받은 add 메서드를 이용하여 스택의 동작을 구현하는 것은 올바른 방법입니다.


pop 메서드:
pop 메서드는 스택의 맨 끝에 있는 요소를 제거하고 반환
현재 구현은 remove 메서드를 호출하여 맨 끝에 있는 요소를 제거하고 반환하고 있습니다.
empty 메서드를 사용하여 스택이 비어있는지 여부를 확인하고 처리하는 부분이 잘 구현되어 있습니다.


peek 메서드:
peek 메서드는 스택의 맨 끝에 있는 요소를 조회
현재 구현은 get 메서드를 호출하여 맨 끝에 있는 요소를 반환하고 있습니다.


empty 메서드:
empty 메서드는 스택이 비어있는지 여부를 반환합니다.
현재 구현은 size 메서드를 호출하여 스택의 크기를 확인하고, 크기가 0인지 여부를 반환하고 있습니다.


 

이렇게  생성된 스택과 큐는 MenuPrompt 클래스를 구성하는데 사용된다.

MenuPrompt 는 Menu와 관련한 기능을 한다.

 

인스턴스 변수로 Queue, Stack의 설계도에 따른 파라미터 객체

commandHistory, menus, breadcrumbs 를 사용한다.

 

Queue, Stack 는 LinkedList의 서브 클래스이므로 상속 받은 메서드를 사용할 수 있다.

또한 LinkedList는 수퍼 클래스 List의 인터페이스 규칙인 add, get, toArray, remove(object), remove (boolean)을 

상속 받아 사용한다.


MenuPrompt의 기능

MenuPrompt의 기능을 하는 메서드는 다음과 같다.

 

appendBreadcrumb(String title, String menu)
메서드 설명: 현재 경로와 메뉴를 스택에 추가
기능: 현재 경로와 해당 메뉴를 breadcrumbs 스택과 menus 스택에 각각 추가

removeBreadcrumb()
메서드 설명: 현재 경로와 메뉴를 스택에서 제거
기능: breadcrumbs 스택과 menus 스택에서 현재 경로와 해당 메뉴를 각각 제거


printMenu()
메서드 설명: 현재 메뉴를 출력
기능: menus 스택에서 최상단에 있는 메뉴를 출력

 

printCommandHistory()
메서드 설명: 사용자의 명령어 히스토리를 출력
기능: commandHistory 큐에 저장된 사용자의 명령어 히스토리를 출력

 

inputMenu()
메서드 설명: 사용자로부터 메뉴 입력을 받음
기능: 사용자로부터 메뉴를 입력받아 유효성을 검사하고, 

"history"나 "menu"인 경우에는 각각의 메서드를 호출하여 히스토리 또는 현재 메뉴를 출력

그 외에는 입력받은 메뉴를 반환

 

findMenuItem(String command)
메서드 설명: 입력받은 메뉴 번호에 해당하는 메뉴를 찾음
기능: 현재 메뉴에서 입력받은 메뉴 번호와 일치하는 메뉴를 찾아 반환

 

 

MenuPrompt의 코드

package bitcamp.util;

public class MenuPrompt extends Prompt {

  private Queue commandHistory = new Queue();
  private Stack menus = new Stack();
  private Stack breadcrumbs = new Stack();

  public void appendBreadcrumb(String title, String menu) {
    this.breadcrumbs.push(title);
    this.menus.push(menu);
  }

  public void removeBreadcrumb() {
    this.breadcrumbs.pop();
    this.menus.pop();
  }

  public void printMenu() {
    System.out.println(this.menus.peek());
  }


  public void printCommandHistroy() {
    for (int i = 0; i < this.commandHistory.size(); i++) {
      System.out.println(this.commandHistory.get(i));
    }
  }

  public String inputMenu() {
    StringBuilder titleBuilder = new StringBuilder();// 예) 메인/회원>
    for (int i = 0; i < this.breadcrumbs.size(); i++) {
      if (titleBuilder.length() > 0) {
        titleBuilder.append("/");
        // 기존에 경로가 있다면 구분해주기 위해서 "/"를 표시
      }
      titleBuilder.append(this.breadcrumbs.get(i));
    }
    titleBuilder.append("> ");

    String command = null;

    while (true) {
      command = this.inputString(titleBuilder.toString());
      if (command.equals("history")) {
        printCommandHistroy();
      } else if (command.equals("menu")) {
        this.printMenu();
      } else if (findMenuItem(command) == null) {
        System.out.println("메뉴 번호가 옳지 않습니다.");
      } else {
        break;
      }
    }

    if (this.commandHistory.size() == 10) {
      // 사용자가 입력한 명령어를 history에 보관
      // 명령어 목록은 최대 10개만 유지한다.
      // 10개를 초과할 경우 맨 앞에 기록을 삭제한다.
      this.commandHistory.poll();
    }
    String menuItem = findMenuItem(command);
    if (menuItem != null) {
      this.commandHistory.offer(titleBuilder.toString() + ": " + menuItem);
    } else {
      this.commandHistory.offer(command);
    }
    return command;
  }


  private String findMenuItem(String command) {
    String menuTitle = null;
    // 메뉴에서 command에 해당하는 메뉴가 있다면 그 메뉴 이름을 리턴하고
    // 없다면 null을 리턴한다.

    // 1) 현재 메뉴를 알아낸다. 메뉴 스택에서 맨 마지막에 입력한 메뉴를 조회한다.
    String menu = (String) this.menus.peek();

    // 2) 꺼낸 메뉴에서 해당 번호의 메뉴를 찾는다.
    String[] menuItems = menu.split("\n");
    for (String menuItem : menuItems) {
      if (menuItem.startsWith(command)) {
        return menuItem;
      }
    }

    return menuTitle;
  }



}

내일 배우는 것

 

 

1. 메뉴구성 

디자인 패턴 - composite 패턴
시작은 루트메뉴.
하나의 메뉴가 다른 메뉴를 포함한다.



2. 디자인 패턴 - 커맨드 패턴
메서드들을 클래스로 분리한다. Member(5개) Board(5개) = 10개
서블릿 프로그램을 배울 때 핵심 기술로 필요하다.


3. 입력

디자인 패턴 - observer 패턴
메뉴와 클래스를 연결하는 것은 observer 패턴을 이용한다.
메뉴가 추가될 때 클래스를 추가한다.

이전에는 MemberHanlder 클래스를 열어서 
search 라는 기능을 포함한 메서드를 추가하는데,
search 멤버 서치 커맨드 클래스를 만든다.
이렇게하면 새로운 코드를 추가할 때 기존 코드를 수정하지 않아도 된다.

 

반응형