209페이지 커피 예제를 보고 자유롭게 작성, 책 참고 가능
기능 목록
도메인 모델
민환

수빈

우진
구현 코드
민환
손님 (Customer.java)
public class Customer { private final Barista barista; private final Menu menu; public Customer(Barista barista, Menu menu) { this.barista = barista; this.menu = menu; } // 결과값 확인을 위해 반환 public Coffee order(String menuName) { MenuItem menuItem = menu.getMenuByName(menuName); return barista.makeCoffee(menuItem); } }
메뉴 (Menu.java)
public class Menu { private final List<MenuItem> menuItems; public Menu(List<MenuItem> menuItems) { this.menuItems = menuItems; } public MenuItem getMenuByName(String menuName) { return menuItems.stream() .filter(menuItem -> menuItem.isSameName(menuName)) .findAny() .orElseThrow(() -> new IllegalArgumentException(String.format("%s에 해당하는 메뉴가 없습니다.", menuName))); } }
메뉴 아이템 (MenuItem.java)
public class MenuItem { private final String name; private final int price; public MenuItem(String name, int price) { this.name = name; this.price = price; } public boolean isSameName(String menuName) { return name.equals(menuName); } public String getName() { return name; } public int getPrice() { return price; } }
바리스타 (Barista.java)
public class Barista { public Coffee makeCoffee(MenuItem menuItem) { return new Coffee(menuItem); } }
커피(Coffee.java)
public class Coffee { private final String name; private final int price; public Coffee(String name, int price) { this.name = name; this.price = price; } public Coffee(MenuItem menuItem) { this(menuItem.getName(), menuItem.getPrice()); } // 결과값 확인을 위해 사용 @Override public String toString() { return "Coffee{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
Main
public class CoffeeShop { public static void main(String[] args) { List<MenuItem> menuItems = List.of( new MenuItem("아메리카노", 1500), new MenuItem("카푸치노", 2000), new MenuItem("카라멜 마키야또", 2500), new MenuItem("에스프레소", 2500) ); Menu menu = new Menu(menuItems); Barista barista = new Barista(); Customer customer = new Customer(barista, menu); Coffee coffee = customer.order("아메리카노"); System.out.println(coffee); } }
궁금한 점
- 책에서는 각 객체를 메서드 파라미터로 넘겨주었다. 필드로 가지고 있는 것과 파라미터로 넘겨주는 것 어떤 것이 더 좋은 설계일까?
개인 의견
검색과 생각해본 결과 객체의 비즈니스 로직을 많이 사용한다면 필드로 빼는 것이 좋을 것 같다. 즉, 동일한 매개 변수를 여러 메서드에서 사용하는 지 여부를 확인하자!
- MenuItems라는 일급 컬렉션을 만들려고 했지만 Menu 자체가 일급 컬렉션이다.
- get과 find 네이밍의 차이에 대해서 알아봅시다.
- findById ⇒ Optional
- getRerenceById ⇒ T , throw
Menu를 enum으로 사용한다면?
public enum Menu { AMERICANO("아메리카노", 1500), CAPPUCCINO("카푸치노", 2000), CARAMEL_MACCHIATO("카라멜 마키야또", 2500), ESPRESSO("에스프레소", 2500); private static final Map<String, Menu> MENU_MAP = new HashMap(); static { for (Menu menu : values()) { MENU_MAP.put(menu.getName(), menu); } } private final String name; private final int price; Menu(String name, int price) { this.name = name; this.price = price; } public static Menu getMenuByName(String menuName) { if (!MENU_MAP.containsKey(menuName)) { throw new IllegalArgumentException(String.format("%s에 해당하는 메뉴가 없습니다.", menuName)); } return MENU_MAP.get(menuName); } public String getName() { return name; } public int getPrice() { return price; } }
- map 사용은 한번 해보고 싶었습니다…
- 장점
- enum을 사용하면 MenuItem을 생성할 필요가 없으며 확장하고 싶은 경우 상수에 추가만 해주면 된다.
- (개인 의견) enum은 정적인 경우에만 사용하는 것이 좋을 것 같다.
- 단점
- enum이기 때문에 상태가 변화할 수 없다.
- 아메리카노의 가격을 500원으로 변경해야한다면 변경 후 어플리케이션을 재배포해야함.
- 상속이나 확장이 일부 제한되기 때문에 유연성이 조금 떨어진다.
그러면 어떻게 해야할까?
- 처음 예제처럼 MenuItem 도메인으로 구현 후 데이터베이스에서 처리
수빈
협력 : 커피를 주문해라
@Test void 커피주문_협력() { Customer customer = new Subin(); Cafe cafe = new StarBucks(); cafe.provideService(customer); // 카페 : 손님에게 서비스를 제공할 책임 }
Cafe의 책임
interface Cafe { void provideService(Customer customer); }
Cafe의 역할(책임)을 수행하는 스타벅스
class StarBucks implements Cafe { Cashier cashier = new Alice(); Barista barista = new Rabbit(); List<Menu> menus = List.of(Menu.AMERICANO, Menu.CAFE_LATTE); public List<Menu> getMenus() { return new ArrayList<>(menus); } @Override public void provideService(Customer customer) { List<Menu> menu = this.getMenus(); // 카페 : 메뉴판 제공할 책임 Menu selectedMenu = customer.selectMenu(menu); // 손님 : 메뉴를 선택할 책임 var coffee = cashier.takeOrder(selectedMenu, barista); // 캐시어 : 선택된 메뉴를 바리스타에게 전달할 책임 customer.drinkCoffee(coffee); // 손님 : 커피를 마실 책임 } }
Customer의 책임
interface Customer { Menu selectMenu(List<Menu> menus); void drinkCoffee(Coffee coffee); }
Customer의 역할(책임)을 수행하는 Subin 객체
class Subin implements Customer { private final Random random = new Random(); @Override public Menu selectMenu(List<Menu> menus) { var index = random.nextInt(menus.size()); return menus.get(index); } @Override public void drinkCoffee(Coffee coffee) { System.out.println(coffee + " 마시는 중..."); } }
Cashier와 Barisata의 책임
interface Cashier { Coffee takeOrder(Menu selected, Barista barista); } interface Barista { Coffee makeCoffee(Menu coffee); }
Cashier와 Barisata의 역할(책임)을 수행하는 Alice와 Rabbit 객체
class Alice implements Cashier { @Override public Coffee takeOrder(Menu selected, Barista barista) { return barista.makeCoffee(selected); } } class Rabbit implements Barista { @Override public Coffee makeCoffee(Menu coffee) { return Coffee.valueOf(coffee.toString()); } }
Menu랑 Coffee
enum Menu { HAZELNUT, AMERICANO, CAPPUCCINO, CAFE_LATTE; } enum Coffee { HAZELNUT, AMERICANO, CAPPUCCINO, CAFE_LATTE; }
우진
메인
public class Main { public static void main(String[] args) { Customer customer = new Customer(); Coffee coffee = customer.order(new Cashier(), "americano"); } }
메뉴
public enum Menu { AMERICANO("americano", 3000), CAFE_LATTE("caffeLatte", 4000), ESPRESSO("espresso", 3500), CAPPUCCINO("cappuccino", 3500); private final String menuName; private final int price; Menu(String menuName, int price) { this.menuName = menuName; this.price = price; } public static Menu selectMenu(String menuName) { return Arrays.stream(values()) .filter(menu -> menu.menuName.equals(menuName)) .findAny() .orElseThrow(() -> new IllegalArgumentException("판매하지 않는 메뉴입니다.")); } public String getMenuName() { return menuName; } public int getPrice() { return price; } }
고객
public class Customer { public Coffee order(Cashier cashier, String menuName) { Menu menu = Menu.selectMenu(menuName); return cashier.order(menu, new Barista()); } }
캐시어
public class Cashier { public Coffee order(Menu menu, Barista barista) { return barista.makeCoffee(menu); } }
바리스타
public class Barista { public Coffee makeCoffee(Menu menu) { return new Coffee(menu); } }
커피
public class Coffee { private String name; private int price; public Coffee(Menu menu) { this.name = menu.getMenuName(); this.price = menu.getPrice(); } }