메뉴선택, 장바구니, 주문하기, 할인 기능들을 넣어서 햄버거 가게 키오스크 기능을 구현해 봤다.
처음에는 다향성을 고려하지 않고 만들어 메뉴추가 or 할인조건 변경 등 유지보수 측면에서 안좋은 모습을 보였는데 아래 나열한 코드와 같이 모두 주석처리하고 다시 상속과 구현을 활용해 유지보수가 쉽게 바꿧다.
메뉴를 선택헤 장바구니에 담고 똑같은 상품을 다시 장바구니에 담으면 이전에 주문한 내용의 옵션들이 나중에 주문한 내역 때문에 덮어 씌여지는 버그가 생겼었다. 이는 객체를 독립적으로 생성해주지 않고 공유하기 때문에 발생하는 문제였고 인스턴스 생성에 주의가 필요하다는 것을 알 수 있었다.
package app;
import app.discount.Discount;
import app.discount.discountCondition.CozDiscountCondition;
import app.discount.discountCondition.DiscountCondition;
import app.discount.discountCondition.KidDiscountCondition;
import app.discount.discountPolicy.FixedAmountDiscountPolicy;
import app.discount.discountPolicy.FixedRateDiscountPolicy;
import app.product.Product;
import app.product.ProductRepository;
import java.util.Scanner;
public class OrderApp {
private ProductRepository productRepository;
private Menu menu;
private Cart cart;
private Order order;
public OrderApp(ProductRepository productRepository, Menu menu, Cart cart, Order order) {
this.productRepository = productRepository;
this.menu = menu;
this.cart = cart;
this.order = order;
}
public void start() {
Scanner scanner = new Scanner((System.in));
// ProductRepository productRepository = new ProductRepository(); // 객체 생성부분 AppConfiguer 클래스로 따로 뺏다.
// Product[] products = productRepository.getAllProducts();
// Menu menu = new Menu(products);
//
// Cart cart = new Cart(productRepository, menu);
// Order order = new Order(cart, new Discount(
// new DiscountCondition[]{
// new CozDiscountCondition(new FixedRateDiscountPolicy(10)),
// new KidDiscountCondition(new FixedAmountDiscountPolicy(500))
// })
// );
System.out.println("🍔 BrugerQueen Order Service");
while (true) {
menu.printMenu(); // 메뉴 출력
String input = scanner.nextLine(); // 사용자 입력 받기
if (input.equals("+")) {
order.makeOrder(); // 주문 내역 출력
break;
} else {
int menuNumber = Integer.parseInt(input); // 입력 문자가 +가 아닌 경우에 입력을 숫자로 바꾸기 위해 사용
if (menuNumber == 0) cart.printCart(); // 0입력시 장바구니 보여주기
else if (1 <= menuNumber && menuNumber <= productRepository.getAllProducts().length) cart.addToCart(menuNumber); // 장바구니 담기 기능
}
}
}
}
package app;
import app.discount.Discount;
import app.discount.discountCondition.CozDiscountCondition;
import app.discount.discountCondition.DiscountCondition;
import app.discount.discountCondition.KidDiscountCondition;
import app.discount.discountPolicy.FixedAmountDiscountPolicy;
import app.discount.discountPolicy.FixedRateDiscountPolicy;
public class Order {
private Cart cart; // cart 객체를 통째로 필드로 정의
// private DiscountCondition[] discountConditions; // Discount 클래스로 이동
private Discount discount;
// public Order(Cart cart, DiscountCondition[] discountConditions){ // Discount 클래스 만들어서 아래처럼 바뀜
// this.cart = cart;
// this.discountConditions = discountConditions;
// }
public Order(Cart cart, Discount discount) {
this.cart = cart;
this.discount = discount;
}
public void makeOrder() {
// CozDiscountCondition cozDiscountCondition = new CozDiscountCondition(new FixedRateDiscountPolicy(10)); // implement를 쓰면 이렇게 일일히 할 필요 없음.
// KidDiscountCondition kidDiscountCondition = new KidDiscountCondition(new FixedAmountDiscountPolicy(500));
// cozDiscountCondition.checkDiscountCondition();
// kidDiscountCondition.checkDiscountCondition();
//
// int totalPrice = cart.calculateTotalPrice();
//
// int finalPrice = totalPrice;
// if (cozDiscountCondition.isSatisfied()) finalPrice = cozDiscountCondition.applyDiscount(finalPrice);
// if (kidDiscountCondition.isSatisfied()) finalPrice = kidDiscountCondition.applyDiscount(finalPrice);
discount.checkAllDiscountConditions(); // 할인조건 전부 출력
int totalPrice = cart.calculateTotalPrice();
int finalPrice = discount.discount(totalPrice);
//
// for (DiscountCondition discountCondition : discountConditions) { //할인에 관한 내용 Discount 클래스로 따로 빼기
// discountCondition.checkDiscountCondition();
// if(discountCondition.isSatisfied()){
// finalPrice = discountCondition.applyDiscount(finalPrice);
// }
// }
System.out.println("[📣] 주문이 완료되었습니다. ");
System.out.println("[📣] 주문 내역은 다음과 같습니다. ");
System.out.println("-".repeat(60));
cart.printcartItemDetails();
System.out.println("-".repeat(60));
System.out.printf("금액 합계 : %d원\n", totalPrice);
System.out.printf("할인 적용 금액 : %d원\n", finalPrice);
}
}
package app;
import app.product.Product;
import app.product.subproduct.Drink;
import app.product.subproduct.Hamburger;
import app.product.subproduct.Side;
public class Menu {
private Product[] products;
public Menu(Product[] products) {
this.products = products;
}
public void printMenu() {
System.out.println("[🔻] 메뉴");
System.out.println("-".repeat(60));
printHamburgers(true);
printSides(true);
printDrinks(true);
System.out.println();
System.out.println("🧺 (0) 장바구니");
System.out.println("📦 (+) 주문하기");
System.out.println("-".repeat(60));
System.out.print("[📣] 메뉴를 선택해주세요 : ");
}
private void printHamburgers(boolean printPrice) {
System.out.println("🍔 햄버거");
//여기에서 햄버거 출력
for (Product product : products) {
if (product instanceof Hamburger) {
printEachMenu(product, printPrice);
}
}
System.out.println();
}
protected void printSides(boolean printPrice) {
System.out.println("🍟 사이드");
//여기에서 사이드 출력
for (Product product : products) {
if (product instanceof Side) {
printEachMenu(product, printPrice);
}
}
System.out.println();
}
protected void printDrinks(boolean printPrice) {
System.out.println("🥤 음료");
//여기에서 음료 출력
for (Product product : products) {
if (product instanceof Drink) {
printEachMenu(product, printPrice);
}
}
System.out.println();
}
private static void printEachMenu(Product product, boolean printPrice) {
if (printPrice) System.out.printf(
" (%,d) %s %,5dKcal %,5d원\n",
product.getId(), product.getName(), product.getKcal(), product.getPrice()
);
else System.out.printf(" (%d) %s %5dKcal\n", product.getId(), product.getName(), product.getKcal());
}
}
package app;
public class Main {
public static void main(String[] args) {
AppConfigurer appConfigurer = new AppConfigurer();
OrderApp orderApp = new OrderApp(
appConfigurer.productRepository(),
appConfigurer.menu(),
appConfigurer.cart(),
appConfigurer.order()
);
orderApp.start();
}
}
package app;
import app.product.Product;
import app.product.ProductRepository;
import app.product.subproduct.BurgerSet;
import app.product.subproduct.Drink;
import app.product.subproduct.Hamburger;
import app.product.subproduct.Side;
import java.util.Scanner;
public class Cart {
private Product[] items = new Product[0]; //초기 길이=0, 물건 담을때 마다 길이 1씩 늘리면 됨.
private Scanner scanner = new Scanner(System.in);
public void printCart() {
System.out.println("🧺 장바구니");
System.out.println("-".repeat(60));
printcartItemDetails();// 장바구니 상품들을 욥션 정보와 함께 출력
System.out.println("-".repeat(60));
System.out.printf("합계 :%d원\n", calculateTotalPrice());
System.out.println("이전으로 돌아가려면 엔터를 누르세요. ");
scanner.nextLine();
}
protected void printcartItemDetails() { // 장바구니 상품(옵션 정보 포함) 출력
for (Product product : items) {
if (product instanceof BurgerSet) {
BurgerSet burgerSet = (BurgerSet) product;
System.out.printf(
" %s %6d원 (%s(케첩 %d개), %s(빨대 %s))\n",
product.getName(),
product.getPrice(),
burgerSet.getSide().getName(),
burgerSet.getSide().getKetchup(),
burgerSet.getDrink().getName(),
burgerSet.getDrink().hasStraw() ? "있음" : "없음"
);
} else if (product instanceof Hamburger) {
System.out.printf(
" %-8s %6d원 (단품)\n",
product.getName(),
product.getPrice()
);
} else if (product instanceof Side) {
System.out.printf(
" %-8s %6d원 (케첩 %d개)\n",
product.getName(),
product.getPrice(),
((Side) product).getKetchup() // 아래 설명 참고
);
} else if (product instanceof Drink) {
System.out.printf(
" %-8s %6d원 (빨대 %s)\n",
product.getName(),
product.getPrice(),
((Drink) product).hasStraw() ? "있음" : "없음" // 아래 설명 참고
);
}
}
}
protected int calculateTotalPrice() { //장바구니 금액 합계
int totalPrice=0;
for (Product product : items) {
totalPrice +=product.getPrice();
}
return totalPrice;
}
private ProductRepository productRepository;
private Menu menu; // 세트 옵션 선택 과정에서 메뉴를 부분적으로 보여주기 위해 추가
public Cart(ProductRepository productRepository, Menu menu){
this.productRepository = productRepository;
this.menu = menu;
}
public void addToCart(int productID){
Product product = productRepository.findById(productID); // productID로 id로 갖는 상품 찾기
chooseOption(product);
if(product instanceof Hamburger){ // product가 햄거버&&세트면 세트구성하기
Hamburger hamburger = (Hamburger) product;
if (hamburger.isBurgerSet()) product = composeSet(hamburger);
}
Product newProduct;
if(product instanceof Hamburger) newProduct = new Hamburger((Hamburger) product); //참조값이 다른 새로운 객체 생성(깊은복사)
else if (product instanceof Side) newProduct = new Side((Side) product); // 이렇게 객체 따로 안만들어주고 이 메서드가 여러번 실행되면
else if (product instanceof Drink) newProduct = new Drink((Drink) product); // 뒤에 입력한 값이 앞에서 입력한 값을 덮어버린다.
else newProduct = new BurgerSet((BurgerSet) product); // 버거셋은 그냥 newProduct = product 하고 오버로딩 안해도 된다.
Product[] newItems = new Product[items.length+1]; // 배열길이 늘려주기
System.arraycopy(items, 0,newItems, 0, items.length); // 기존 배열 붙여넣기
newItems[newItems.length-1] = newProduct; // 마지막 요소에 newproduct 넣기
items = newItems;
}
private void chooseOption(Product product) {
String input;
if (product instanceof Hamburger) {
System.out.printf("단품으로 주문하시겠어요? (1)_단품(%d원) (2)_세트(%d원)\n",
product.getPrice(), ((Hamburger) product).getBurgerSetPrice());
input = scanner.nextLine();
if (input.equals("2")) ((Hamburger)product).setIsBurgerSet(true);
}
else if (product instanceof Side){
System.out.println("케첩은 몇개가 필요하신가요?");
input = scanner.nextLine();
((Side) product).setKetchup(Integer.parseInt(input)); // Integer.parseInt(input) << input(String)을 int로 변환
}
else if (product instanceof Drink) {
System.out.println("빨대가 필요하신가요? (1)_예 (2)_아니오");
input = scanner.nextLine();
if (input.equals("2")) ((Drink) product).setHasStraw(false);
}
}
private BurgerSet composeSet(Hamburger hamburger){
System.out.println("사이드를 골라주세요");
menu.printSides(false);
String sideID = scanner.nextLine();
Side side = (Side) productRepository.findById(Integer.parseInt(sideID));
chooseOption(side);
System.out.println("음료를 골라주세요.");
menu.printDrinks(false);
String drinkId = scanner.nextLine();
Drink drink = (Drink) productRepository.findById(Integer.parseInt(drinkId));
chooseOption(drink);
String name = hamburger.getName() + "세트";
int price = hamburger.getBurgerSetPrice();
int kcal = hamburger.getKcal() + side.getKcal() + drink.getKcal();
return new BurgerSet(name, price, kcal, hamburger, side, drink);
}
}
package app;
import app.discount.Discount;
import app.discount.discountCondition.CozDiscountCondition;
import app.discount.discountCondition.DiscountCondition;
import app.discount.discountCondition.KidDiscountCondition;
import app.discount.discountPolicy.FixedAmountDiscountPolicy;
import app.discount.discountPolicy.FixedRateDiscountPolicy;
import app.product.ProductRepository;
public class AppConfigurer {
private Cart cart = new Cart(productRepository(), menu());
public ProductRepository productRepository() {
return new ProductRepository();
}
public Menu menu() {
return new Menu(productRepository().getAllProducts());
}
public Cart cart() {
//return new Cart(productRepository(), menu()); // 이렇게 하면 cart가 order에서 한번, main에서 한번 두번 생성 되어 서로 다른 인스턴스가 되어버림
return cart;
}
public Discount discount() { // 할인요건, 할인폭 결정 메소드
return new Discount(
new DiscountCondition[]{
new CozDiscountCondition(new FixedRateDiscountPolicy(10)),
new KidDiscountCondition(new FixedAmountDiscountPolicy(500))
});
}
public Order order() {
return new Order(cart(), discount());
}
}
package app.product;
import app.product.subproduct.Drink;
import app.product.subproduct.Hamburger;
import app.product.subproduct.Side;
public class ProductRepository {
private Product[] products = {
new Hamburger(1,"새우버거", 3500,500,false, 4500),
new Hamburger(2, "치킨버거", 4000, 600, false, 5000),
new Side(3, "감자튀김", 1000, 300, 1),
new Side(4, "어니언링", 1000, 300, 1),
new Drink(5, "코카콜라", 1000, 200, true),
new Drink(6, "제로콜라", 1000, 0, true),
};
public Product[] getAllProducts() {
return products;
}
public Product findById(int productId) { //결합도 낮추기 위한 메소드
for (Product product : products) {
if (product.getId() == productId) return product;
}
return null;
}
}
package app.product;
public class Product {
private int id;
private String name;
private int price;
private int kcal;
public Product(int id, String name, int price, int kcal) {
this.id=id;
this.name=name;
this.price=price;
this.kcal=kcal;
}
public Product(String name, int price, int kcal) {
this.name = name;
this.price = price;
this.kcal = kcal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getKcal() {
return kcal;
}
public void setKcal(int kcal) {
this.kcal = kcal;
}
}
package app.product.subproduct;
import app.product.Product;
public class Side extends Product {
private int ketchup;
public Side(int id, String name, int price, int kcal, int ketchup) {
super(id, name, price, kcal);
this.ketchup = ketchup;
}
public Side(Side side) {
super(side.getName(), side.getPrice(), side.getKcal());
this.ketchup = side.getKetchup();
}
public int getKetchup() {
return ketchup;
}
public void setKetchup(int ketchup) {
this.ketchup = ketchup;
}
}
package app.product.subproduct;
import app.product.Product;
public class Hamburger extends Product {
private boolean isBurgerSet;
private int burgerSetPrice;
public Hamburger(int id, String name, int price, int kcal, boolean isBurgerSet, int burgerSetPrice) {
super(id,name,price,kcal);
this.isBurgerSet = isBurgerSet;
this.burgerSetPrice = burgerSetPrice;
}
public Hamburger(Hamburger hamburger){
super(hamburger.getName(),hamburger.getPrice(),hamburger.getKcal());
this.isBurgerSet = hamburger.isBurgerSet(); // hamburger. 빼도 똑같지 않나?
this.burgerSetPrice = hamburger.getBurgerSetPrice(); // 왜 hamburger.get으로 안해도 문제가 없을까?..
}
public boolean isBurgerSet(){
return isBurgerSet;
} // 왜 get으로 안하고?? get역할하는 메소드 이름을 이렇게 만들었지? 자동 생성하면 이렇게 나오긴하는데..
public void setIsBurgerSet(boolean isburgerSet) {
this.isBurgerSet = isburgerSet;
}
public int getBurgerSetPrice() {
return burgerSetPrice;
}
public void setBurgerSetPrice(int burgerSetPrice) {
this.burgerSetPrice = burgerSetPrice;
}
}
package app.product.subproduct;
import app.product.Product;
public class Drink extends Product {
private boolean hasStraw;
public Drink(int id, String name, int price, int kcal, boolean hasStraw) {
super(id, name, price, kcal);
this.hasStraw = hasStraw;
}
public Drink(Drink drink) {
super(drink.getName(), drink.getPrice(), drink.getKcal());
this.hasStraw = drink.hasStraw();
}
public boolean hasStraw() {
return hasStraw;
}
public void setHasStraw(boolean hasStraw) {
this.hasStraw = hasStraw;
}
}
package app.product.subproduct;
import app.product.Product;
public class BurgerSet extends Product {
private Hamburger hamburger;
private Side side;
private Drink drink;
public BurgerSet(String name, int price, int kcal, Hamburger hamburger, Side side, Drink drink) {
super(name, price, kcal);
this.hamburger = hamburger;
this.side = side;
this.drink = drink;
}
public BurgerSet(BurgerSet burgerSet) {
super(burgerSet.getName(), burgerSet.getPrice(), burgerSet.getKcal());
this.hamburger = new Hamburger(burgerSet.hamburger);
this.side = new Side(burgerSet.side);
this.drink = new Drink(burgerSet.drink);
}
public Hamburger getHamburger() {
return hamburger;
}
public Side getSide() {
return side;
}
public Drink getDrink() {
return drink;
}
}
package app.discount;
import app.discount.discountCondition.DiscountCondition;
public class Discount {
private DiscountCondition[] discountConditions;
public Discount(DiscountCondition[] discountConditions) {
this.discountConditions = discountConditions;
}
public void checkAllDiscountConditions() { // 할인요건 전부 출력
for (DiscountCondition discountCondition : discountConditions) {
discountCondition.checkDiscountCondition();
}
}
public int discount(int price) { // 할인 조건에 해당하면 할인 적용 후 최종가격 출력
int discountedPrice = price;
for (DiscountCondition discountCondition : discountConditions) {
if (discountCondition.isSatisfied()) discountedPrice = discountCondition.applyDiscount(discountedPrice);
}
return discountedPrice;
}
}
package app.discount.discountCondition;
public interface DiscountCondition {
void checkDiscountCondition();
int applyDiscount(int price);
boolean isSatisfied();
}
package app.discount.discountCondition;
import app.discount.discountPolicy.DiscountPolicy;
import app.discount.discountPolicy.FixedRateDiscountPolicy;
import java.util.Scanner;
public class CozDiscountCondition implements DiscountCondition {
public boolean isSatisfied;
public boolean isSatisfied() {
return isSatisfied;
}
private void setSatisfied(boolean satisfied) {
isSatisfied = satisfied;
}
//private FixedRateDiscountPolicy fixedRateDiscountPolicy = new FixedRateDiscountPolicy(10);
private DiscountPolicy discountPolicy;
public CozDiscountCondition(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public void checkDiscountCondition() {
Scanner scanner = new Scanner(System.in);
System.out.println("코드스테이츠 수강생이십니까? (1)_예 (2)아니오");
String input = scanner.nextLine();
if(input.equals("1")) setSatisfied(true);
else if (input.equals("2")) setSatisfied(false);
}
public int applyDiscount(int price) {
//return fixedRateDiscountPolicy.calculateDiscountedPrice(price);
return discountPolicy.calculateDiscountedPrice(price);
}
}
package app.discount.discountCondition;
import app.discount.discountPolicy.DiscountPolicy;
import app.discount.discountPolicy.FixedAmountDiscountPolicy;
import java.util.Scanner;
public class KidDiscountCondition implements DiscountCondition{
private boolean isSatisfied;
//private FixedAmountDiscountPolicy fixedAmountDiscountPolicy = new FixedAmountDiscountPolicy(500);
private DiscountPolicy discountPolicy;
public KidDiscountCondition(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public boolean isSatisfied() {
return isSatisfied;
}
private void setSatisfied(boolean satisfied) { // 캡슐화를 위해 Setter의 접근 제어자를 private으로 변경
isSatisfied = satisfied;
}
public void checkDiscountCondition() {
Scanner scanner = new Scanner(System.in);
System.out.println("나이가 어떻게 되십니까?");
int input = Integer.parseInt(scanner.nextLine()); // String타입의 input을 int형으로 변환
setSatisfied(input < 19);
}
public int applyDiscount(int price) {
//return fixedAmountDiscountPolicy.calculateDiscountedPrice(price);
return discountPolicy.calculateDiscountedPrice(price);
}
}
package app.discount.discountPolicy;
public interface DiscountPolicy {
int calculateDiscountedPrice(int price);
}
package app.discount.discountPolicy;
public class FixedAmountDiscountPolicy implements DiscountPolicy {
private int discountAmount;
public FixedAmountDiscountPolicy(int discountAmount) {
this.discountAmount = discountAmount;
}
public int calculateDiscountedPrice(int price){
return price - discountAmount;
}
}
package app.discount.discountPolicy;
public class FixedRateDiscountPolicy implements DiscountPolicy {
private final int discountRate;
public FixedRateDiscountPolicy(int discountRate) {
this.discountRate = discountRate;
}
public int calculateDiscountedPrice(int price) {
return price - (price*discountRate/100);
}
}