Skip to content

[4장_이지훈] #18

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

Merged
merged 3 commits into from
Apr 16, 2023
Merged
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
5 changes: 5 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/Customer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ezhoon.chapter04

class Customer {

}
35 changes: 35 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/Money.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ezhoon.chapter04

import java.math.BigDecimal

data class Money(
private val amount: BigDecimal
) {
fun plus(other: Money): Money {
return Money(this.amount.add(other.amount))
}

fun minus(other: Money): Money {
return Money(this.amount.subtract(other.amount))
}

operator fun times(percent: Double): Money {
return Money(this.amount.multiply(BigDecimal(percent)))
}

fun isLessThan(other: Money) = amount < other.amount

fun isGreaterThan(other: Money) = amount >= other.amount

companion object {
val ZERO = getWons(0)

fun getWons(amount: Long): Money {
return Money(BigDecimal.valueOf(amount))
}

fun getWons(amount: Double): Money {
return Money(BigDecimal.valueOf(amount))
}
}
}
76 changes: 76 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## 설계 품질과 트레이드오프

### 가장 중요한 것은 책임이다.

- 역할, 책임, 협럭 중에서 가장 중요한 것은 책임이다.
- 책임이 적절해야 역할과 협력이 적절하게 조화를 이룬다.
- 그렇기에 책임이 객체지향 어플리케이션 전체의 품질을 결정하게 된다.

> 훌륭한 설계는 합리적인 비용안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
> 적절한 비용 안에서 쉽게 변경할 수 있는 설계는 응집도가 높고, 서로 느슨하게 결합돼 있는 요소로 구성된다.

### 시스템을 객체로 분할하는 방법

객체지향 설계에서는 두 가지 방법을 이용해 시스템을 객체로 분할할 수 있다.
- 상태를 분할의 중심축으로 삼는 방법
- 객체를 독립된 데이터 덩어리로 본다.
- 책임을 분할의 중심축으로 삼는 방법
- 객체를 협력하는 공동체의 일원으로 본다.

## 설계 트레이드오프

### 캡슐화

> 보편적으로 캡슐화라고 하면 객체의 내부 구현을 외부로부터 감추는 것을 의미한다.

변경될 가능성이 높은 부분을 구현이라고 부르고, 상대적으로 안정적인 부분을 인터페이스라고 부른다.

- 이 부분에서 익숙한게 우리가 객체지향을 만들 때 인터페이스를 써야하는 이유랑 겹쳐서 생각하면 편하다.
- 우리가 가변적인 List를 사용해야 한다고 하면 보편적으로 MutableList와 ArrayList를 생각하게 된다.
- 둘의 공통점은 ArrayList를 사용한다.

```kotlin
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
public interface MutableList<E> : List<E>, MutableCollection<E>

public inline fun <T> arrayListOf(): ArrayList<T> = ArrayList()
expect class ArrayList<E> : MutableList<E>, RandomAccess
```

- mutableListOf는 실제로 MutableList interface를 반환하고 arrayList는 ArrayList라는 구현체를 반환한다.
- 이 때 우리는 mutableListOf를 사용해야 한다.
- 이유는 뭘까? 잘 생각해보자.
- 만약 내가 arrayListOf를 사용중인데 kotlin version이 올라가면서 기본 arrayList대신 더 좋은 가변 list가 나온다면?
- arrayListOf를 사용중인 곳을 찾아서 전부 바꿔줘야 하고 그것은 개발자에게 실수를 야기할 수 있다.
- 하지만 mutableListOf의 경우 MutableList라는 interface이고 나중에 실제 구현체가 다른 것으로 변경이 돼도 우린 신경을 안써도 된다.
- 이처럼 인터페이스는 구현보다 상대적으로 안정적인 부분이라는 것을 알 수 있다.

> 추가적으로 ArrayList도 잘 보면 MutableList interface를 구현한 객체이다.
> 그렇기에 mutableListOf에서 실제 구현체가 ArrayList가 될 수 있던 것이고 반환으로 MutableList interface가 가능했던 것이다.


### 응집도와 결합도

응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 의미한다.

결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도이다.


## 자율적인 객체를 향해

> 객체는 스스로의 상태를 책임져야 하며, 외부에서는 인터페이스에 정의된 메서드를 통해서만 상태에 접근할 수 있어야 한다.

### 스스로 자신의 데이터를 책임지는 객체

객체는 단순히 데이터 제공자가 아니다.

- 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다.
- 이 객체가 어떤 데이터를 포함해야 하는가?
- 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가??

> 최근 구현에서 비슷한 경우가 있었다.
> RecyclerView를 사용하면서 ViewHolder에서 2가지 종류가 존재하는데 하나의 객체로 처리할지 2개의 객체로 처리할지 이다.
> 이전의 구현에서는 1개의 객체로 구현을 했는데 그러면 각각의 종류에서 필요하지 않는 데이터를 포함하는 경우가 생겼다.
> 그래서 객체지향 관점에서 수정이 필요하다고 판단을 해서 리팩토링을 했던 것이 기억이 난다.


10 changes: 10 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/Reservation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ezhoon.chapter04

import ezhoon.chapter04.movie.Screening

class Reservation(
private val customer: Customer,
private val screening: Screening,
private val fee: Money,
private val audienceCount: Int
)
11 changes: 11 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/ReservationAgency.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ezhoon.chapter04

import ezhoon.chapter04.movie.Screening

class ReservationAgency {

fun reserve(screening: Screening, customer: Customer, audienceCount: Int): Reservation? {
val fee = screening.calculateFee(audienceCount)
return Reservation(customer, screening, fee, audienceCount)
}
}
44 changes: 44 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/discount/DiscountCondition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ezhoon.chapter04.discount

import ezhoon.chapter04.movie.Screening
import java.time.DayOfWeek
import java.time.LocalTime

class DiscountCondition(
val type: DiscountConditionType,
private val sequence: Int,
private val dayOfWeek: DayOfWeek,
private val startTime: LocalTime,
private val endTime: LocalTime,
) {
fun isDiscountable(dayOfWeek: DayOfWeek, time: LocalTime): Boolean {
if (type != DiscountConditionType.Period) {
throw IllegalArgumentException()
}

return isSameDayOfWeek(dayOfWeek) && isBeforeOrEqualEndTime(time) && isAfterOrEqualStartTime(time)
}

fun isDiscountable(sequence: Int): Boolean {
if (type != DiscountConditionType.Sequence) {
throw IllegalArgumentException()
}
return this.sequence == sequence
}

private fun isBeforeOrEqualEndTime(
time: LocalTime
): Boolean = endTime >= time

private fun isAfterOrEqualStartTime(
time: LocalTime
): Boolean = startTime <= time

private fun isSameDayOfWeek(
dayOfWeek: DayOfWeek
): Boolean = dayOfWeek == this.dayOfWeek

private fun isSameSequence(
screening: Screening
): Boolean = sequence == screening.sequence
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ezhoon.chapter04.discount

sealed interface DiscountConditionType {

object Sequence : DiscountConditionType
object Period : DiscountConditionType
}
49 changes: 49 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/movie/Movie.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ezhoon.chapter04.movie

import ezhoon.chapter04.Money
import ezhoon.chapter04.discount.DiscountCondition
import ezhoon.chapter04.discount.DiscountConditionType
import java.time.Duration
import java.time.LocalDateTime

data class Movie(
val title: String,
val runningTime: Duration,
val fee: Money,
val discountConditions: List<DiscountCondition>,
val movieType: MovieType,
val discountAmount: Money,
val discountPercent: Double
) {
/**
* Movie가 어떤 데이터를 포함해야 하는가?
*
* 영화 요금 계산, 할인 여부 판단? discountConditions가 존재하니까?
*/

fun calculateAmountDiscountedFee(): Money {
require(movieType is MovieType.AmountDiscount)
return fee.minus(discountAmount)
}

fun calculatePercentDiscountedFee(): Money {
require(movieType is MovieType.PercentDiscount)
return fee.minus(fee.times(discountPercent))
}

fun calculateNoneDiscountedFee(): Money {
require(movieType is MovieType.NoneDiscount)
return fee
}

fun isDiscountable(whenScreened: LocalDateTime, sequence: Int): Boolean {
return discountConditions.any { it.isDiscountable(whenScreened, sequence) }
}

private fun DiscountCondition.isDiscountable(whenScreened: LocalDateTime, sequence: Int): Boolean {
return when (type) {
DiscountConditionType.Period -> isDiscountable(whenScreened.dayOfWeek, whenScreened.toLocalTime())
DiscountConditionType.Sequence -> isDiscountable(sequence)
}
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/movie/MovieType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ezhoon.chapter04.movie

sealed interface MovieType {

object AmountDiscount : MovieType
object PercentDiscount: MovieType
object NoneDiscount: MovieType
}
22 changes: 22 additions & 0 deletions src/main/kotlin/ezhoon/chapter04/movie/Screening.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ezhoon.chapter04.movie

import ezhoon.chapter04.Money
import java.time.LocalDateTime

class Screening(
val movie: Movie,
val sequence: Int,
private val whenScreened: LocalDateTime
) {
fun calculateFee(audienceCount: Int): Money {
val fee = when {
movie.isDiscountable(whenScreened, sequence) -> when (movie.movieType) {
MovieType.AmountDiscount -> movie.calculateAmountDiscountedFee()
MovieType.PercentDiscount -> movie.calculatePercentDiscountedFee()
MovieType.NoneDiscount-> movie.calculateNoneDiscountedFee()
}
else -> movie.calculateNoneDiscountedFee()
}
return fee * audienceCount.toDouble()
}
}