Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions kotlin-christmas/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ repositories {

dependencies {
implementation("com.github.woowacourse-projects:mission-utils:1.1.0")
implementation("org.reflections:reflections:0.10.2")
implementation("org.jetbrains.kotlin-reflection:1.9.0")
implementation(kotlin("reflect"))
}

java {
Expand Down
8 changes: 7 additions & 1 deletion kotlin-christmas/src/main/kotlin/christmas/Application.kt
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()
}
29 changes: 29 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/ChristmasGameManager.kt
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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

달이 바뀐다면 매번 다시 입력해줘야하니까 귀찮지 않을까요? 나머지를 통해 평일인지 구별하는게 좋을 것 같아요!

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 }


}
13 changes: 13 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/domain/Badge.kt
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),

}
25 changes: 25 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/domain/Menu.kt
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),
}
9 changes: 9 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/domain/MenuType.kt
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,
}
39 changes: 39 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/domain/OrderMenu.kt
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
62 changes: 62 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/global/Container.kt
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 {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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(".")
}

}
58 changes: 58 additions & 0 deletions kotlin-christmas/src/main/kotlin/christmas/view/InputView.kt
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("숫자만 입력해 주세요."))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문제조건에 메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다. 이렇게 되어있어서 20개까지 주문 가능할 것 같아요!

"$ERROR_PREFIX 20개 이상의 음식은 주문하실 수 없습니다."
}

return OrderMenu(userVisitDay, orders)
}

}
Loading