음식점에서 음식 주문하는 과정 구현
요구사항
* 1. 도메인을 구성하는 객체에는 어떤 것들이 있는지 고민
* ㄴ 손님, 메뉴판(0) , 돈까스/냉면(0), 요리사(0) ,요리(0)
* 2. 객체들 간의 관계를 고민
* ㄴ 손님 -- 메뉴판
* ㄴ 손님 -- 요리사
* ㄴ 요리사 -- 요리
* 3. 동적인 객체를 정적인 타입으로 추상화해서 도메인 모델링 하기
* ㄴ 손님 -- 손님 타입
* ㄴ 돈까스/냉면/만두 -- 요리 타입
* ㄴ 메뉴판 -- 메뉴판 타입
* ㄴ 메뉴 -- 메뉴 타입
* 4. 협력을 설계
* 5. 객체들을 포괄하는 타입에 적절한 책임을 할당
* 6. 구현하기
package org.example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThatCode;
/**
* 음식점에서 음식 주문하는 과정 구현
* 요구사항
* 1. 도메인을 구성하는 객체에는 어떤 것들이 있는지 고민
* ㄴ 손님, 메뉴판(0) , 돈까스/냉면(0), 요리사(0) ,요리(0)
* 2. 객체들 간의 관계를 고민
* ㄴ 손님 -- 메뉴판
* ㄴ 손님 -- 요리사
* ㄴ 요리사 -- 요리
* 3. 동적인 객체를 정적인 타입으로 추상화해서 도메인 모델링 하기
* ㄴ 손님 -- 손님 타입
* ㄴ 돈까스/냉면/만두 -- 요리 타입
* ㄴ 메뉴판 -- 메뉴판 타입
* ㄴ 메뉴 -- 메뉴 타입
* 4. 협력을 설계
* 5. 객체들을 포괄하는 타입에 적절한 책임을 할당
* 6. 구현하기
*/
public class CustomerTest {
@DisplayName("메뉴이름에 해당하는 요리를 주문한다.")
@Test
void orderTest(){
Customer customer = new Customer();
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000),new MenuItem("냉면",7000)));
Cooking cooking = new Cooking();
assertThatCode(() -> customer.order("돈까스", menu, cooking))
.doesNotThrowAnyException();
}
}
Customer customer = new Customer();
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000),new MenuItem("냉면",7000)));
Cooking cooking = new Cooking();
- Customer, Menu, Cooking 클래스의 인스턴스 생성하고 변수에 할당한다. 이것은 테스트에서 사용할 객체들을 초기화하는 부분 이다.
- Customer 는 손님 객체, Menu 는 메뉴판 객체, Cooking 은 요리사 객체를 나타낸다
- Menu 객체는 MenuItem 객체 리스트를 가지며 돈까스 와 냉면 메뉴 항목을 초기화하고 가격을 설정한다.
assertThatCode(() -> customer.order("돈까스", menu, cooking))
.doesNotThrowAnyException();
- customer.order("돈까스", menu, cooking) 은 customer 객체가 "돈까스" 를 주문하고 처리하는 메서드를 호출하는 코드이다.
- doesNotThrowAnyException() 은 코드 블록이 예외를 던지지 않아야 함을 검증하는 부분이다.
위 코드들의 목적은 객체 지향 설계 원칙을 따르는 방식으로 음식 주문과정을 모델링하고 테스트를 통해 코드가 예상대로 동작하는지 확인하는 것이다.
package org.example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatCode;
public class CookTest {
@DisplayName("요리를 생성한다.")
@Test
void createTest(){
assertThatCode(()->new Cook("만두",5000))
.doesNotThrowAnyException();
}
}
- CookTest 클래스는 요리를 생성하는 Cook 클래스 테스트를 수행한다.
assertThatCode(()->new Cook("만두",5000))
.doesNotThrowAnyException();
- new Cook("만두",5000) 은 Cook 클래스의 객체를 생성하는 코드이다. "만두"라는 요리를 생성하고 가격을 설정
이 코드 목적은 Cook 클래스의 객체를 생성하는 행위를 테스트하는 것이다
코드 자체는 테스트 코드로 Cook 클래스가 요구사항에 따라 제대로 동작하는지 확인하기 위함이다
여기서는 만두 라는 음식을 생성하고 예외가 발생하지 않는다면 테스트가 통과한다.
package org.example;
import java.util.Objects;
public class Cook {
private final String name;
private final int price
;
public Cook(String name, int price) {
this.name = name;
this.price = price;
}
public Cook(MenuItem menuItem) { //
this.name = menuItem.getName();
this.price = menuItem.getPrice();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cook cook = (Cook) o;
return price == cook.price && Objects.equals(name, cook.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
- 요리를 나타내느 클래스이다.
private final String name;
private final int price
- Cook 클래스의 필드로 요리의 이름을 나타내는 name (문자열) 필드와 요리의 가격을 나타내는 price( 정수 ) 필드를 선언한다 이러한 필드의 객체의 상태를 나타낸다.
public Cook(String name, int price) {
this.name = name;
this.price = price;
- Cook 클래스의 생성자다 이 생성자는 요리의 이름과 가격을 인수로 받아 객체를 초기화한다.
- this.name=name; 은 생성자 매개변수 name 값을 객체의 name 필드에 할당한다
- this.price = price 는 생성자 매개변수 price 값을 객체의 price 필드에 할당한다.
public Cook(MenuItem menuItem) { //
this.name = menuItem.getName();
this.price = menuItem.getPrice();
- 또 다른 생성자이다 이생성자는 MenuItem 객체를 받아와서 Cook 객체로 변환한다.
- MenuItem 객체에서 이름과 가격을 가져와서 Cook 객체의 필드에 할당한다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Cook cook = (Cook) o;
return price == cook.price && Objects.equals(name, cook.name);
- equals 메서드를 오버라이드 한다 이 메서드는 두 개의 객체가 동등한지 여부를 판단하는 데 사용된다.
- this 와 O 가 같은 객체를 참조하는지 확인한다 만약 그렇다면 두 객체는 동일하므로 true 를 반환한다.
그렇지 않으면 O가 null 이거나 O 의 클래스가 Cook 클래스가 아닌 경우에는 false 를 반환한다.
- Objects.equals(name, cook.name)은 name 필드와 cook.name 필드를 비교하여 두 객체의 이름이 동일한지를 확인한다
- price == cook.price는 두 객체의 가격이 동일한지 확인하고 이 모든 조건이 충족되면 두 객체는 동등하다고 판단하고 true를 반환한다.
@Override
public int hashCode() {
return Objects.hash(name, price);
- hasCode 메서드를 오버라이드 한다 이 메서드는 객체의 해시 코드를 반환 한다.
- Objects.hash(name, price) 는 name 필드와 price 필드를 기반으로 한 해시 코드를 생성하여 반환한다. 이렇게 함으로써
동긍한 객체는 동일한 해시 코드를 갖게 된다.
위 코드들은 Cook을 정의하며 객체의 생성, 동등성 비교, 해시 코드 생성을 다루는 기본적인 구현을 제공한다.
package org.example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatCode;
public class MenuitemTest {
@DisplayName("메뉴항목을 생성한다.")
@Test
void createTest() {
assertThatCode(()->new MenuItem("만두",5000))
.doesNotThrowAnyException();
}
}
MenuItem 클래스의 테스트를 수행하는 테스트 클래스이다.
assertThatCode(()->new MenuItem("만두",5000))
.doesNotThrowAnyException();
new MenuItem("만두", 5000) 는 MenuItem 클래스의 객체를 생성하는 코드이다 만두라는 메뉴 항목을 생성하고 가격을 설정한다.
이 코드들의 목적은 MenuItem 클래스의 객체를 생성하는 행위를 테스트하는 것이다 MenuItem 클래스 가 요구사항에 따라 제대로 동작하는지 확인하기 위해 사용된다.
package org.example;
import java.util.Objects;
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 matches(String name) {
return this.name.equals(name);
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MenuItem menuItem = (MenuItem) o;
return price == menuItem.price && Objects.equals(name, menuItem.name);
}
@Override
public int hashCode() {
return Objects.hash(name, price);
}
}
이 클래스는 메뉴 항목을 나타내며 메뉴 항목의 이름과 가격을 갖는 클래스이다 클래스 내부에서 생성자, matches 메서드 게터 메서트 , equals 메서드 , hasCode 메서드를 구현하고 있다.
public MenuItem(String name, int price) {
this.name = name;
this.price = price;
- MenuItem 클래스의 생성자이다 이 생성자는 메뉴 항목의 이름과 가격을 인수로 받아 객체를 초기화한다.
- this.name = name;은 생성자 매개변수 name 값을 객체의 name 필드에 할당한다.
- this.price = price;은 생성자 매개변수 price 값을 객체의 price 필드에 할당.
public boolean matches(String name) {
return this.name.equals(name);
- matches 메서드는 주어진 메뉴 항목의 이름이 인수로 전달된 이름과 일치하는지 여부를 반환
- this.name.equlas(name) 은 객체의 name 필드와 인수로 전달된 name 문자열을 비교하여 일치 여부 확인.
public String getName() {
return name;
}
public int getPrice() {
return price;
}
getName : 메서드는 메뉴 항목 이름을 반환한다.
getPrice : 메서드는 메뉴 항목의 가격을 반환한다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MenuItem menuItem = (MenuItem) o;
return price == menuItem.price && Objects.equals(name, menuItem.name);
- equals 메서드를 오버라이한다 이메서드는 두개의 객체가 동등한지 여부를 판단하는데 사용된다
- this와 o가 같은 객체를 참조하는지 확인합니다. 만약 그렇다면 두 객체는 동일하므로 true를 반환
그렇지 않으면, o가 null이거나 o의 클래스가 MenuItem 클래스가 아닌 경우에는 false를 반환
- Objects.equals(name, menuItem.name)는 name 필드와 menuItem.name 필드를 비교하여 두 객체의 이름이 동일한지를 확인
- price == menuItem.price는 두 객체의 가격이 동일한지 확인합니다. 이 모든 조건이 충족되면 두 객체는 동등하다고 판단하고 true를 반환.
@Override
public int hashCode() {
return Objects.hash(name, price);
- hashCode 메서드를 오버라이드합니다. 이 메서드는 객체의 해시 코드를 반환
- Objects.hash(name, price)는 name 필드와 price 필드를 기반으로 한 해시 코드를 생성하여 반환
이렇게 함으로써 동등한 객체는 동일한 해시 코드를 갖게된다.
위 코드들은 간단한 데이터 클래스인 MenuItem를 정의하며, 객체의 생성, 동등성 비교, 해시 코드 생성을 다루는 기본적인 구현을 제공
package org.example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThatCode;
public class CookingTest {
@DisplayName("메뉴에 해당하는 음식을 만든다.")
@Test
void makeCookTest() {
Cooking cooking = new Cooking(); //요리사에게 요리사 객체를 만들고
MenuItem menuItem = new MenuItem("돈까스",5000); // 요리를 만든다음에
Cook cook = cooking.makeCook(menuItem); //요리사에게 요리를 만들어 달라는 요청을 한다. (메뉴를 보고 요리를 해준다.)
assertThatCode(()-> new Cook("돈까스",5000)) //돈까스에 해당하는 요리가 맞나요 ?
.doesNotThrowAnyException();
}
}
Cooking 클래스의 makeCook 메서드를 테스트 하는 코드이다.
- 해당 테스트는 Cooking 클래스가 메뉴 항목을 기반으로 요리를 만드는지 검증하는 것이 목적
Cooking cooking = new Cooking(); //요리사에게 요리사 객체를 만들고
MenuItem menuItem = new MenuItem("돈까스",5000); // 요리를 만든다음에
- 테스트 수행하기 위해 Cooking 클래스와 menuItem 클래스의 객체를 생성한다
- Cooking 클래스의 객체 cooking 을 생성하고 MenuItem 클래스의 객체 menuItem 을 생성한다.
Cook cook = cooking.makeCook(menuItem); //요리사에게 요리를 만들어 달라는 요청을 한다. (메뉴를 보고 요리를 해준다.)
- cooking.makeCook(menuItem) 는 Cooking 클래스의 makeCook 메서드를 호출하여 요리를 만들어 달라는 요청 한다.
이 메서드는 menuItem 객체를 기반으로 요리를 만들어 Cook 클래스의 객체 cook을 반환한다.
assertThatCode(()-> new Cook("돈까스",5000)) //돈까스에 해당하는 요리가 맞나요 ?
.doesNotThrowAnyException();
- new Cook("돈까스" , 5000) 는 돈까스 라는 요리를 생성하는 코드이다.
위 코드들의 주요 목적은 Cooking 클래스의 makeCook 메서드를 테스트하여 메뉴 항목에 해당하는 요리를 올바르게 만드는지 확인하는 것이다.
package org.example;
public class Cooking {
public Cook makeCook(MenuItem menuItem) {
Cook cook = new Cook(menuItem);
return cook;
}
}
- 이 클래스에는 요리를 만드는 메서드인 makeCook가 있다. makeCook 메서드는 MenuItem 객체를 입력으로 받아 해당 메뉴 항목을 기반으로 요리를 생성하고 반환 .
public Cook makeCook(MenuItem menuItem) {
- makeCook 메서드를 정의한다 이 메서드는 MenuItem 객체를 입력으로 받아 해당 메뉴 항목을 기반으로 요리를 만들어 반환
Cook cook = new Cook(menuItem);
- Cook 클래스의 객체 cook 을 생성하고 이 객체를 MenuItem 객체 menuItem 을 사용하여 초기화 한다.
return cook;
- makeCook 메서드는 요리가 완성된 후 cook 객체를 반환한다.
위 코드들의 목적은 Cooking 클래스의 makeCook 메서드를 통해 메뉴 항목을 기반으로 요리를 만드는 동작을 수행한다.
클래스 자체는 간단한 요리 생성을 수행하는 역활을 가지며 Cook 클래스와 MenuItem 클래스를 사용하여 요리를 만들어 반환.
package org.example;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
public class MenuTest {
@DisplayName("메뉴판에서 메뉴이름에 해당하는 메뉴를 반환한다.")
@Test
void chooseTest() {
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000), new MenuItem("냉면",7000)));
MenuItem menuItem = menu.choose("돈까스");
assertThat(menuItem).isEqualTo(new MenuItem("돈까스",5000));
}
@DisplayName("메뉴판에 없는 메뉴를 선택할시 예외를 반환한다.")
@Test
void chooseTest2() {
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000), new MenuItem("냉면",7000)));
assertThatCode(() -> menu.choose("통닭"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("잘못된 메뉴 이름입니다.");
}
}
- Menu 클래스는 메뉴 항목 목록을 가지고 있으며, 주어진 메뉴 이름에 해당하는 메뉴 항목을 반환하는 기능을 제공
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000), new MenuItem("냉면",7000)));
- Menu 클래스의 객체 menu를 생성합니다. 이 객체는 메뉴 항목 목록을 가지고 있으며, 초기 메뉴 항목으로 "돈까스"와 "냉면"을 가짐
MenuItem menuItem = menu.choose("돈까스");
- menu.choose("돈까스")는 Menu 클래스의 choose 메서드를 호출하여 메뉴 이름이 "돈까스"인 메뉴 항목을 선택
- 선택된 메뉴 항목은 MenuItem 객체 menuItem에 할당
assertThat(menuItem).isEqualTo(new MenuItem("돈까스",5000));
- assertThat 메서드를 사용하여 menuItem 객체가 주어진 MenuItem 객체와 동일한지를 검증 즉, "돈까스" 메뉴 항목이 올바르게 선택되었는지 확인
Menu menu = new Menu(List.of(new MenuItem("돈까스",5000), new MenuItem("냉면",7000)));
- menu 객체를 다시 생성하고, 동일한 메뉴 항목 리스트로 초기화
assertThatCode(() -> menu.choose("통닭"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("잘못된 메뉴 이름입니다.");
- assertThatCode 메서드를 사용하여 menu.choose("통닭") 코드 블록을 검증
- .isInstanceOf(IllegalArgumentException.class)는 예외가 IllegalArgumentException 클래스의 인스턴스여야 함을 검증
- .hasMessage("잘못된 메뉴 이름입니다.")는 예외의 메시지가 "잘못된 메뉴 이름입니다."여야 함을 검증
위 코드들은 Menu 클래스의 두 가지 동작을 테스트
첫 번째 테스트는 메뉴에서 메뉴 항목을 선택하고, 선택된 메뉴 항목이 올바른지 확인
두 번째 테스트는 존재하지 않는 메뉴 항목을 선택하면 예외가 발생하는지 확인
package org.example;
import java.util.List;
public class Menu {
private final List<MenuItem> menuItem;
public <E> Menu(List<MenuItem> menuItems) {
this.menuItem = menuItems;
}
public MenuItem choose(String name) {
return this.menuItem.stream()
.filter(menuItem1 -> menuItem1.matches(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 메뉴 이름입니다."));
}
}
Menu 클래스는 메뉴 항목 목록을 관리하고, 메뉴 항목을 선택하는 기능을 제공
이 클래스는 제네릭 <E>를 사용하여 메뉴 항목 목록을 저장하며, choose 메서드를 통해 메뉴 항목을 선택한다.
private final List<MenuItem> menuItem;
- MenuItem 클래스의 목록을 저장하기 위한 menuItem 필드를 선언한다.\
이 필드는 메뉴 항목을 저장하고 관리하는데 사용
public <E> Menu(List<MenuItem> menuItems) {
this.menuItem = menuItems;
- Menu 클래스의 생성자를 정의 이 생성자는 MenuItem 클래스의 목록을 인수로 받아서 menuItem 필드를 초기화한다.
- <E>는 제네릭 타입 매개변수로 사용되지만, 이 경우에는 제네릭을 사용하지 않고 MenuItem 목록을 받는다.
- 제네릭 타입 매개변수가 제대로 활용되지 않았으므로 이 부분은 수정이 필요할수는 있다.
public MenuItem choose(String name) {
return this.menuItem.stream()
.filter(menuItem1 -> menuItem1.matches(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("잘못된 메뉴 이름입니다."));
- choose 메서드는 메뉴에서 메뉴 항목을 선택하는 역할
이 메서드는 메뉴 항목의 이름을 인수로 받아 해당 이름과 일치하는 메뉴 항목을 반환
- 메뉴 항목 목록인 menuItem을 스트림으로 변환하고, filter 메서드를 사용하여 주어진 이름과 일치하는 메뉴 항목을 필터링
- findFirst 메서드를 사용하여 첫 번째 일치하는 메뉴 항목을 선택하며, 일치하는 메뉴 항목이 없는 경우 orElseThrow 메서드를 사용하여 IllegalArgumentException 예외를 던진다.
이렇게 함으로써 메뉴 항목을 선택하고, 선택된 메뉴 항목이 존재하지 않는 경우 예외를 처리할 수 있다.
위 코드들은 메뉴 항목 목록을 관리하고, 주어진 이름에 해당하는 메뉴 항목을 선택하는 기능을 제공하는 간단한 Menu 클래스를 나타낸다.
Java 스트림과 람다 표현식을 사용하여 메뉴 항목을 선택하는 부분을 간결하게 구현함.
package org.example;
public class Customer {
public void order(String menuName, Menu menu, Cooking cooking){
MenuItem menuItem = menu.choose(menuName);
Cook cook = cooking.makeCook(menuItem);
}
}
이 클래스는 음식 주문 프로세스를 구현하는 데 사용
order 메서드를 통해 주문을 처리하고, 이 메서드는 메뉴 항목의 이름, 메뉴, 그리고 요리사를 받아서 주문을 처리
public void order(String menuName, Menu menu, Cooking cooking){
- order 메서드를 정의합니다. 이 메서드는 음식 주문을 처리
- menuName은 주문할 메뉴 항목의 이름을 나타낸다.
- menu는 주문할 메뉴 항목을 선택하기 위한 Menu 객체를 나타낸다.
- cooking은 주문을 처리하고 요리를 만들기 위한 Cooking 객체를 나타낸다.
MenuItem menuItem = menu.choose(menuName);
- menu.choose(menuName)은 menu 객체의 choose 메서드를 호출하여 주문할 메뉴 항목을 선택
- menu.choose 메서드는 메뉴에서 주문할 메뉴 항목을 선택하고 해당 메뉴 항목을 반환
Cook cook = cooking.makeCook(menuItem);
- cooking.makeCook(menuItem)은 cooking 객체의 makeCook 메서드를 호출하여 요리를 만든다.
- cooking.makeCook 메서드는 주어진 MenuItem 객체를 기반으로 요리를 만들어 Cook 객체를 반환한다.
위 코드들의 목적은 Customer 클래스를 사용하여 음식 주문을 처리하는 것
order 메서드를 호출하면 메뉴 항목을 선택하고, 선택된 메뉴 항목을 기반으로 요리를 만들어 요리사에게 주문을 처리
이 코드는 객체 간의 협력을 나타내고, 주문 프로세스를 구현하는 데 사용한다.
'JAVA > 객체지향' 카테고리의 다른 글
객체지향 프로그래밍(2) (0) | 2023.10.30 |
---|---|
객체지향 프로그래밍(1) (1) | 2023.10.29 |
객체지향 개념정리 (1) | 2023.10.27 |
01. 테스트코드 (0) | 2023.10.26 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!