-
Notifications
You must be signed in to change notification settings - Fork 0
[산타] 짱구 미션 제출합니다. #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 02ggang9-main
Are you sure you want to change the base?
Changes from all commits
5bb9b48
f7282b2
60a847f
362327e
9456421
a1f7151
a1b9c52
09f6d45
bf4d8b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,11 @@ | ||
| package christmas | ||
|
|
||
| import christmas.global.Container | ||
|
|
||
| class Application | ||
|
|
||
| fun main() { | ||
| TODO("프로그램 구현") | ||
| Container.componentScan(Application::class) | ||
| val christmasGameManager = Container.getInstance(ChristmasGameManager::class) | ||
| christmasGameManager.run() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package christmas | ||
|
|
||
| import christmas.global.Component | ||
| import christmas.view.InputView | ||
| import christmas.view.OutputView | ||
|
|
||
| @Component | ||
| class ChristmasGameManager( | ||
| private val inputView: InputView, | ||
| private val outputView: OutputView, | ||
| ) { | ||
|
|
||
| fun run() { | ||
| // STEP1 > 식당 예상 방문 날짜 입력 받기 | ||
| outputView.printEventMessage() | ||
| val userVisitDay = inputView.getVisitDay() | ||
|
|
||
| // STEP2 > 주문할 메뉴와 개수 입력 받기 | ||
| outputView.printMenuAndCountMessage() | ||
| val ordersDto = inputView.getOrderMenuAndCount(userVisitDay) | ||
|
|
||
| // STEP3 > 이벤트 혜택 미리보기 출력 | ||
| outputView.printEventListMessage(userVisitDay) | ||
|
|
||
| // STEP4 > 주문 메뉴 출력 | ||
| outputView.printReceipt(ordersDto) | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| import christmas.domain.OrderMenu | ||
| import christmas.global.Component | ||
|
|
||
| @Component | ||
| class ChristmasDdayDiscountPolicy: EventDiscountPolicy { | ||
|
|
||
| override val discountPolicyName: String | ||
| get() = "크리스마스 디데이 할인" | ||
|
|
||
| override fun isSatisfiedBy(orderMenu: OrderMenu): Boolean = | ||
| orderMenu.reservationDate in 1..25 | ||
|
|
||
| override fun calculateDiscountAmount(orderMenu: OrderMenu): Long = | ||
| 1000L + 100L * (orderMenu.reservationDate - 1) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| import christmas.domain.OrderMenu | ||
|
|
||
| interface EventDiscountPolicy { | ||
|
|
||
| val discountPolicyName: String | ||
|
|
||
| fun isSatisfiedBy(orderMenu: OrderMenu): Boolean | ||
|
|
||
| fun calculateDiscountAmount(orderMenu: OrderMenu): Long | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| fun createEventDiscountPolicy(): List<EventDiscountPolicy> = | ||
| listOf(ChristmasDdayDiscountPolicy(), SpecialDiscountPolicy(), WeekdayDiscountPolicy(), WeekendDiscountPolicy()) | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| import christmas.domain.OrderMenu | ||
| import christmas.global.Component | ||
|
|
||
| @Component | ||
| class SpecialDiscountPolicy: EventDiscountPolicy { | ||
|
|
||
| override val discountPolicyName: String | ||
| get() = "특별 할인" | ||
|
|
||
| override fun isSatisfiedBy(orderMenu: OrderMenu): Boolean { | ||
| val days = listOf(3, 10, 17, 24, 25, 31) | ||
| return orderMenu.reservationDate in days | ||
| } | ||
|
|
||
| override fun calculateDiscountAmount(orderMenu: OrderMenu): Long = 1000L | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| import christmas.domain.MenuType | ||
| import christmas.domain.OrderMenu | ||
| import christmas.global.Component | ||
|
|
||
| @Component | ||
| class WeekdayDiscountPolicy: EventDiscountPolicy { | ||
|
|
||
| override val discountPolicyName: String | ||
| get() = "평일 할인" | ||
|
|
||
| override fun isSatisfiedBy(orderMenu: OrderMenu): Boolean { | ||
| val days = listOf(3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 24, 25, 26, 27, 28, 31) | ||
| return orderMenu.reservationDate in days | ||
| } | ||
|
|
||
| override fun calculateDiscountAmount(orderMenu: OrderMenu): Long = | ||
| 2023L * orderMenu.orderList.entries | ||
| .filter { it.key.menuType == MenuType.DESSERT } | ||
| .sumOf { it.value } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package christmas.discount_policy | ||
|
|
||
| import christmas.domain.MenuType | ||
| import christmas.domain.OrderMenu | ||
| import christmas.global.Component | ||
|
|
||
| @Component | ||
| class WeekendDiscountPolicy: EventDiscountPolicy { | ||
|
|
||
| override val discountPolicyName: String | ||
| get() = "주말 할인" | ||
|
|
||
| override fun isSatisfiedBy(orderMenu: OrderMenu): Boolean { | ||
| val days = listOf(1, 2, 8, 9, 15, 16, 22, 23, 29, 30) | ||
| return orderMenu.reservationDate in days | ||
| } | ||
|
|
||
| override fun calculateDiscountAmount(orderMenu: OrderMenu): Long = | ||
| 2023L * orderMenu.orderList.entries | ||
| .filter { it.key.menuType == MenuType.MAIN } | ||
| .sumOf { it.value } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package christmas.domain | ||
|
|
||
| enum class Badge( | ||
| val badgeName: String, | ||
| val badgePrice: Long | ||
| ) { | ||
|
|
||
| SANTA("산타", 20_000L), | ||
| TREE("트리", 10_000L), | ||
| STAR("별", 5_000L), | ||
| NOTHING("없음", 0L), | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package christmas.domain | ||
|
|
||
| enum class Menu( | ||
| val mainMenuName: String, | ||
| val mainMenuPrice: Long, | ||
| val menuType: MenuType | ||
| ) { | ||
| MUSHROOM_SOUP("양송이수프", 6_000, MenuType.APPETIZER), | ||
| TAPAS("타파스", 5_500, MenuType.APPETIZER), | ||
| CAESAR_SALAD("시저샐러드", 8_000, MenuType.APPETIZER), | ||
|
|
||
| T_BONE_STEAK("티본스테이크", 55_000, MenuType.MAIN), | ||
| BARBECUE_RIB("바비큐립", 54_000, MenuType.MAIN), | ||
| SEAFOOD_PASTA("해산물파스타", 35_000, MenuType.MAIN), | ||
| CHRISTMAS_PASTA("크리스마스파스타", 25_000, MenuType.MAIN), | ||
|
|
||
| CHOCOLATE_CAKE("초코케이크", 15_000, MenuType.DESSERT), | ||
| ICECREAM("아이스크림", 5_000, MenuType.DESSERT), | ||
|
|
||
| ZERO_COLA("제로콜라", 3_000, MenuType.DRINK), | ||
| RED_WINE("레드와인", 60_000, MenuType.DRINK), | ||
| CHAMPAGNE("샴페인", 25_000, MenuType.DRINK), | ||
|
|
||
| NOTHING("없음", 0L, MenuType.NOTHING), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package christmas.domain | ||
|
|
||
| enum class MenuType { | ||
| APPETIZER, | ||
| MAIN, | ||
| DESSERT, | ||
| DRINK, | ||
| NOTHING, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package christmas.domain | ||
|
|
||
| import christmas.discount_policy.EventDiscountPolicy | ||
| import christmas.discount_policy.createEventDiscountPolicy | ||
|
|
||
|
|
||
| class OrderMenu( | ||
| val reservationDate: Int, | ||
| val orderList: Map<Menu, Int>, | ||
| ) { | ||
| private val eventDiscountPolicies: List<EventDiscountPolicy> = createEventDiscountPolicy() | ||
| val amountBeforeDiscount: Long by lazy { orderList.entries.sumOf { it.key.mainMenuPrice * it.value} } | ||
| val giveMenu: Menu by lazy { if (amountBeforeDiscount > 120_000L) Menu.CHAMPAGNE else Menu.NOTHING } | ||
|
|
||
| val benefitDetails: Map<String, Long> by lazy { | ||
| eventDiscountPolicies.filter { policy -> policy.isSatisfiedBy(this) } | ||
| .associate { policy -> policy.discountPolicyName to policy.calculateDiscountAmount(this) } | ||
| } | ||
|
|
||
| val totalDiscountAmount: Long by lazy { | ||
| if (amountBeforeDiscount > 10_000L) { | ||
| return@lazy giveMenu.mainMenuPrice + benefitDetails.entries.sumOf { it.value } | ||
| } else { | ||
| return@lazy 0 | ||
| } | ||
| } | ||
|
|
||
| val badge: Badge by lazy { | ||
| Badge.entries.first { totalDiscountAmount >= it.badgePrice } | ||
| } | ||
|
|
||
| override fun toString(): String { | ||
| return buildString { | ||
| orderList.entries.forEach { | ||
| append(it.key.mainMenuName + " " + it.value + "개\n") | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| package christmas.global | ||
|
|
||
| annotation class Component |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package christmas.global | ||
|
|
||
| import org.reflections.Reflections | ||
| import kotlin.reflect.KClass | ||
| import kotlin.reflect.KFunction | ||
| import kotlin.reflect.KParameter | ||
| import kotlin.reflect.cast | ||
|
|
||
|
|
||
| object Container { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오.... 사실 뭔지 모르겠지만 멋있습니다 👍👍 |
||
|
|
||
| private val registeredClasses = mutableSetOf<KClass<*>>() | ||
| private val cachedInstances = mutableMapOf<KClass<*>, Any>() | ||
|
|
||
| fun register(clazz: KClass<*>) { | ||
| registeredClasses.add(clazz) | ||
| } | ||
|
|
||
| fun <T: Any> getInstance(type: KClass<T>): T { | ||
| if (type in cachedInstances) { | ||
| return type.cast(cachedInstances[type]) | ||
| } | ||
|
|
||
| val instance = registeredClasses.firstOrNull { clazz -> clazz == type } | ||
| ?.let { clazz -> instantiate(clazz) as T } | ||
| ?: throw IllegalArgumentException("해당 인스턴스 타입을 찾을 수 없습니다.") | ||
|
|
||
| cachedInstances[type] = instance | ||
| return instance | ||
| } | ||
|
|
||
| private fun <T: Any> instantiate(clazz: KClass<T>): T { | ||
| val constructor = findUsableConstructor(clazz) | ||
| val params = constructor.parameters | ||
| .map { param -> getInstance(param.type.classifier as KClass<*>) } | ||
| .toTypedArray() | ||
|
|
||
| return constructor.call(*params) | ||
| } | ||
|
|
||
| private fun <T: Any> findUsableConstructor(clazz: KClass<T>): KFunction<T> = | ||
| clazz.constructors.firstOrNull { constructor -> | ||
| constructor.parameters.isAllRegistered | ||
| }?: throw IllegalArgumentException("사용할 수 있는 생성자가 없습니다") | ||
|
|
||
| private val List<KParameter>.isAllRegistered: Boolean | ||
| get() = this.all { it.type.classifier in registeredClasses } | ||
|
|
||
| fun componentScan(clazz: KClass<*>) { | ||
| val reflections = Reflections(clazz.packageName) | ||
| val jClasses = reflections.getTypesAnnotatedWith(Component::class.java) | ||
| jClasses.forEach { jClasses -> Container.register(jClasses.kotlin) } | ||
| } | ||
|
|
||
| private val KClass<*>.packageName: String | ||
| get() { | ||
| val qualifiedName = this.qualifiedName ?: throw IllegalArgumentException("익명 객체입니다!") | ||
| val hierarchy = qualifiedName.split(".") | ||
| return hierarchy.subList(0, hierarchy.lastIndex).joinToString(".") | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package christmas.view | ||
|
|
||
| import camp.nextstep.edu.missionutils.Console | ||
| import christmas.domain.Menu | ||
| import christmas.domain.MenuType | ||
| import christmas.domain.OrderMenu | ||
| import christmas.global.Component | ||
|
|
||
| @Component | ||
| class InputView { | ||
|
|
||
| companion object { | ||
| const val ERROR_PREFIX = "[ERROR]" | ||
| } | ||
|
|
||
| fun getVisitDay(): Int { | ||
| val visitDay = (Console.readLine() | ||
| ?.toIntOrNull() | ||
| ?: throw IllegalArgumentException("숫자만 입력해 주세요.")) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 class의 Exception들은 어디서 catch하나요?? |
||
|
|
||
| requireNotNull(visitDay in 1..31) { | ||
| IllegalArgumentException("$ERROR_PREFIX 유효하지 않은 날짜입니다. 다시 입력해 주세요.") | ||
| } | ||
|
|
||
| return visitDay | ||
| } | ||
|
|
||
| fun getOrderMenuAndCount(userVisitDay: Int): OrderMenu { | ||
| val orders = mutableMapOf<Menu, Int>() | ||
| val menuName = Menu.entries.associateBy { it.mainMenuName } | ||
|
|
||
| val menuAndCount = Console.readLine() | ||
| ?.split(",") | ||
| ?: throw IllegalArgumentException("입력 값이 없습니다.") | ||
|
|
||
| menuAndCount.forEach { item -> | ||
| val parts = item.split("-") | ||
| val menu = menuName[parts[0]] ?: throw IllegalArgumentException("$ERROR_PREFIX 유효하지 않은 주문입니다. 다시 입력해 주세요.") | ||
| val count = parts[1].toInt() | ||
|
|
||
| require(count > 0) { | ||
| "$ERROR_PREFIX 유효하지 않은 주문입니다. 다시 입력해 주세요." | ||
| } | ||
|
|
||
| orders[menu] = count | ||
| } | ||
|
|
||
| if (orders.keys.all { it.menuType == MenuType.DRINK }) throw IllegalArgumentException("$ERROR_PREFIX 음료만 주문 시, 주문할 수 없습니다.") | ||
|
|
||
|
|
||
| require(orders.values.count() < 20) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 문제조건에 |
||
| "$ERROR_PREFIX 20개 이상의 음식은 주문하실 수 없습니다." | ||
| } | ||
|
|
||
| return OrderMenu(userVisitDay, orders) | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
달이 바뀐다면 매번 다시 입력해줘야하니까 귀찮지 않을까요? 나머지를 통해 평일인지 구별하는게 좋을 것 같아요!