🗒️ 플로우차트


1️⃣ 문제 상황
1. 기존구조
- Menu : 정보 + 상태 + 주문처리
- Order : 주문 처리 일부 담당
- 메뉴가 많아지면 반복문 처리가 어려움 / 카테고리 구분X / 메뉴판 느낌 출력X
2. 역할 나누기
- Menu: 메뉴의 기본정보(이름, 가격)
- Coffee: 커피 메뉴(메뉴 상속)
- Dessert: 디저트 메뉴(메뉴 상속 / 재고여부)
- Order<T extends Menu>: 주문, 결제 처리
- Kiosk<T extends Menu>: 키오스크(메뉴 보여주고 주문 받기)
- CategoryType(enum): 커피/디저트 구분 표지판
2️⃣ Menu 클래스 (부모)
- 추상 클래스 생성(abstract): 직접 객체를 만들 수 X (설계도 역할) / 추상 메서드를 통해 자식이 지켜야 할 규칙 정의
- protected(접근 제어자): 자식 클래스 접근 가능 / 외부 노출X
- 추상 메서드: 자식에게 강제로 구현하게 하는 계약 규약
- getter 쓰는 이유: Kiosk, Order, CategoryType에서 접근
public abstract class Menu { // 추상 클래스 생성 !
protected String name; // 상속을 생각하고 만드는 추상 클래스 이기때문에 protected 로
protected int price; // 진행 -> 외부에서 접근 x, 상속 받은 클래스에서는 접근 ㅇ
public Menu(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return this.name;
}
public int getPrice() {
return this.price;
}
public abstract void printInfo(); // 추상 메서드 ? -> 각 메뉴 타입에 맞게 출력 방식 결정
}
3️⃣ Coffee 클래스 (자식)
- extends Menu: Menu의 필드, 메서드, 규칙을 물려받음
- super: 부모 클래스 생성자 호출하여 이름/가격(필수) 세팅
- @Override printInfo(): 강제 구현 규약
public class Coffee extends Menu {
public Coffee(String name, int price) {
super(name, price);
}
@Override
public void printInfo() { // 추상 메서드에 대한 실제 구현
System.out.println("커피: " + name +" - " + price +"원");
}
}
4️⃣ Dessert 클래스 (자식)
- Coffee 클래스와 동일
- 자식 기능 추가: 재고/품절여부
- 재고 부족 > 주문 불가 / 재고 차감 / 다 팔리면 품절 처리
- public boolean decreaseStock(int quantity) : 주문 수량만큼 재고 줄이는 메서드
public class Dessert extends Menu {
private int stock;
private boolean soldOut;
public Dessert(String name, int price,int stock) {
super(name, price); // 부모 Menu의 생성자 호출
this.stock = stock;
this.soldOut = stock ==0;
}
public boolean decreaseStock(int quantity) { // 주문을 위한 기능 (Order 클래스가 사용)
if(quantity > stock){
return false; // 재고 부족 > 주문 불가
}
stock -= quantity; // 아니면 재고 차감
if(stock ==0){
soldOut =true;
}
return true; // 다 팔렸으면 품절 처리
}
public int getStock() {
return this.stock;
}
public boolean isSoldOut() {
return this.soldOut;
} // 외부 읽기허용
@Override
public void printInfo() {
if(soldOut){
System.out.println("디저트: " + this.name +" - SOLD OUT");
}else {
System.out.println("디저트: " + this.name +" - " + this.price +"원 ("
+ this.stock +"개 남음)");
}
} // 품절이면 sold out, 아니면 가격+재고표기 (다른 출력방식 = 다형성)
}
5️⃣ CategoryType(enum)
- enum: 정해진 값만 가지는 상수 집합 / 카테고리 > 커피와 디저트만 존재
- 메뉴를 카테고리로 구분 / 카테고리의 메뉴판 출력
- displayName: 코트용 이름 != 화면용 이름 / 상수에 출력용 이름 같이 O
- List<? extends Menu>: Menu를 상속받은 어떤 메뉴든 받을 수 O
- 카테고리별 메뉴판 제목, 헤더, 출력
- 메뉴 필터링 + 출력
public enum CategoryType {
COFFEE("커피"),
DESSERT("디저트");
private final String displayName;
CategoryType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public void printMenuList(List<? extends Menu> menus) {
System.out.println("=== " + this.displayName +" 메뉴 ===");
int idx=1; // 메뉴를 상속받은 어떤 메뉴든 받을 수 o
if(this == COFFEE){
System.out.printf("%-5s %-10s %-5s\n","번호","이름","가격");
// 문자열 포맷팅
// % -> 포맷 시작
// - -> 왼쪽 정렬 : ex > %-10s -> 문자열을 왼쪽 정렬
// 안붙이면 오른쪽 정렬
// 중앙은 따로 지정이 없어서 그냥 수동으로 해줘야
// d -> 정수(decimal)
// s -> 문자열(string)
// \n -> 줄바
}else {
System.out.printf("%-5s %-10s %-5s %-5s\n","번호","이름","가격","재고");
}
for(Menu menu : menus){
if(this == COFFEE && menu instanceof Coffee coffee){
// instancof : 객체가 특정 클래스로 만들어진 객체인지 확인.
// enum 상수 COFFEE인지 확인하고, menu 객체가 Coffee인지 확인
System.out.printf("%-5d %-10s %-5d\n", idx++, coffee.getName(), coffee.getPrice());
}else if(this == DESSERT && menu instanceof Dessert dessert){
// 위에란 반대.
// enum 상수 DESSERT 인지 확인하고, menu 객체가 Dessert 로 만들어진지 확인
if(dessert.isSoldOut()){
System.out.printf("%-5d %-10s %-5s %-5s\n", idx++, dessert.getName(),"SOLD OUT",0);
}else {
System.out.printf("%-5d %-10s %-5d %-5d\n", idx++, dessert.getName(), dessert.getPrice(), dessert.getStock());
} // 품절 / 재고 여부 확인 (로직 = 디저트 클래스 / 출력 = 카테고리타입)
}
}
// menus.stream()
// .filter(menu -> (this == COFFEE && menu instanceof Coffee) ||
// (this == DESSERT && menu instanceof Dessert))
// .forEach(menu -> {
// if(menu instanceof Coffee c){
// System.out.printf("%-5d %-10s %-5d\n", idx++, c.getName(), c.getPrice());
// } else if(menu instanceof Dessert d){
// String stockInfo = d.isSoldOut() ? "SOLD OUT" : String.valueOf(d.getStock());
// System.out.printf("%-5d %-10s %-5d %-5s\n", idx++, d.getName(), d.getPrice(), stockInfo);
// }
// });
//-> 요렇게 스트림으로 활용해 볼수 있음 !
System.out.println();
}
}
6️⃣ Order 클래스 (제네릭 활용)
- 메뉴가 주문 가능한지 판단, 주문 처리하는 객체
- class Order<T extends Menu>(제네릭): order는 Menu를 상속받는 타입만 주문으로 받음 > 어떤 메뉴 타입을 다루는지 기억
- menu = Menu 자식 타입 중 하나
- 디저트인 경우에만 재고 확인 및 차감 처리
- 메뉴 정보는 Menu에, 재고 관리는 Dessert에 위임
class Order<T extends Menu> {
private T menu; // 주문 대상(Menu 자식 타입 중 하나)
private int quantity; // 주문 수량
public Order(T menu, int quantity) {
this.menu = menu;
this.quantity = quantity;
}
public void process() {
if(menu instanceof Dessert dessert){ // 디저트만 재고여부O
if(!dessert.decreaseStock(quantity)){
System.out.println(dessert.getName() +" 재고 부족으로 주문 실패!");
return;
} // Order = 가능한지 확인 / Dessert = 재고 계산
}
System.out.println(menu.getName() +" " + this.quantity +"개 주문 완료! 총 금액: " + this.menu.getPrice()*this.quantity +"원");
} // 커피, 디저트 동일 처리
}
7️⃣ Kiosk 클래스 (메뉴 관리 + 주문 + 메뉴판)
- 키오스크 역할을 하는 메인 관리자 클래스
- Menu를 상속한 메뉴만 등록 가능
- 메뉴 등록, 메뉴판 출력, 주문 접수 담당
- 실제 주문 처리와 재고 관리는 Order, Dessert에 위임
- 사용자 흐름을 연결하는 허브 역할
import java.util.*;
class Kiosk<T extends Menu> { // Menu 상속 메뉴만 등록 가능
private List<T> menuList =new ArrayList<>();
private Scanner sc=new Scanner(System.in);
public void addMenu(T menu) {
menuList.add(menu);
} // Main: 메뉴 추가 요청 > 키오스크에서 관리
public void showMenu(CategoryType category){ // 메뉴판 출력
List<Menu> filtered =new ArrayList<>();
for(Menu menu : menuList){
if(category == CategoryType.COFFEE && menu instanceof Coffee) {
filtered.add(menu);
}else if(category == CategoryType.DESSERT && menu instanceof Dessert)
{
filtered.add(menu);
}
}
category.printMenuList(filtered);
} // 어떤 메뉴를 보여줄지 선택 / 카테고리타입: 출력만 > 출력 위임
public void takeOrder(){ // 주문 접수
System.out.println("주문할 메뉴 이름:");
String name= sc.nextLine(); // 사용자에게 메뉴 입력 받음
T selected=null;
for(T menu : menuList){
if(menu.getName().equalsIgnoreCase(name)){
selected = menu;
break;
}
} // 메뉴 검색
if(selected ==null){
System.out.println("해당 메뉴 없음!");
return;
} // 없을 때
System.out.println("수량 입력:");
int quantity= sc.nextInt();
sc.nextLine();
Order<T> order =new Order<>(selected, quantity);
order.process(); // Order에게 주문 위임
}
}
8️⃣ Main 클래스 전체 흐름
import java.util.*;
public class Main {
public static void main(String[] args) {
// 키오스크 생성
Kiosk<Menu> kiosk = new Kiosk<>();
// 메뉴 생성 + 키오스크에 메뉴 추가
kiosk.addMenu(new Coffee("아메리카노", 3000));
kiosk.addMenu(new Coffee("라떼", 3500));
kiosk.addMenu(new Dessert("케익", 4000, 5));
kiosk.addMenu(new Dessert("쿠키", 2000, 10));
Scanner sc = new Scanner(System.in);
boolean running = true;
while (running) {
// 1. 카테고리 선택
System.out.println("카테고리 선택: 1.커피 2.디저트");
int cat = sc.nextInt();
sc.nextLine();
CategoryType category = cat == 1 ? CategoryType.COFFEE : CategoryType.DESSERT;
// 삼항 연산자 라고 하는 방식(조건 ? 참일 때 값 : 거짓일 때 값)
// -> 1번이 입력되면 ? true 라서 처음(CategoryType.COFFE)
// -> 2번이 입력되면? false 라서 두번째 값 선택
// 2. 메뉴판 출력
kiosk.showMenu(category);
// 3. 주문 처리
kiosk.takeOrder();
// 4. 계속 주문 여부
System.out.println("계속 주문? y/n");
String cont = sc.nextLine();
// n이 아니면 메뉴판 다시 출력 후 계속 주문
if (cont.equalsIgnoreCase("n")) {
running = false;
}
}
System.out.println("주문 종료!");
}
}
9️⃣ 요약 / 포인트
- 구조 변화 요약: 4차 구조
Menu → 정보/상태 관리
Coffee / Dessert →Menu 상속
Order → 주문 처리 책임
Kiosk → 메뉴 관리 + 주문 + 메뉴판 출력
CategoryType → 카테고리 + 메뉴판 출력
Main → 흐름 제어
-> 각 클래스/enum가 자기 책임만 담당 → 협업 구조 완성
- 이번 시간 핵심 포인트
* 클래스 간 책임 분리와 협업 구조
* 상속 + 제네릭 활용으로 메뉴 확장성 확보
* enum으로 카테고리 관리 + 메뉴판 출력
* 메뉴 판과 주문 처리의 실무 느낌 구현
'🔌 SPARTA > Courses' 카테고리의 다른 글
| 완전 탐색과 그리디 알고리즘 (1) | 2026.01.27 |
|---|---|
| 알고리즘 개념 (1) | 2026.01.27 |
| 걷기반 3회차: 생성자, this, Order (0) | 2026.01.21 |
| 걷기반 2회차: 배열(Array와 List) (0) | 2026.01.16 |
| 자바 개념 확장 (0) | 2026.01.15 |