🔌 SPARTA/Courses

걷기반 4회차: 상속과 제네릭, enum

eunjiom 2026. 1. 23. 20:54

🗒️ 플로우차트

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