Skip to content
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

5단계 - 자동차 경주(리팩터링) #918

Open
wants to merge 17 commits into
base: relkimm
Choose a base branch
from

Conversation

relkimm
Copy link

@relkimm relkimm commented Nov 21, 2022

안녕하세요 인근님~!
5단계 우선 완료되어서 리뷰 요청 드립니다.🙇‍♂️

@relkimm relkimm marked this pull request as ready for review November 21, 2022 15:25
Comment on lines +11 to +21
constructor(car: Car) : this(
id = car.id,
name = car.name,
position = car.position,
)

fun move(movePolicy: MovePolicy): Car {
if (movePolicy.canMove()) {
this.position = this.position.forward()
}
return Car(car = this)
Copy link
Author

@relkimm relkimm Nov 21, 2022

Choose a reason for hiding this comment

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

immutable 하게 만들어 보았습니다~🙆‍♂️

Choose a reason for hiding this comment

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

새로운 car 객체를 잘 생성해주신 거 같은데요~
position 프로퍼티를 불변(val)으로 가지고 있어야 진정한 불변 객체가 아닐까 싶습니다! 🤔
현재는 car 객체의 position 값이 var 로 선언되어있네요~

Comment on lines +3 to 8
class CarGroup(private var cars: List<Car>) {
fun move() {
this.cars.forEach { car ->
this.cars = this.cars.map { car ->
val oil = OilStation.generateOilRandomly()
car.move(movePolicy = OilPolicy(oil = oil))
}
Copy link
Author

@relkimm relkimm Nov 21, 2022

Choose a reason for hiding this comment

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

CarGroupmove 할 때도 immutable 하게 cars 를 반환할까 싶었는데!
아래 getPositions api 를 사용하는 것도 괜찮은 것 같아서 그대로 두었습니다.😭😭

Choose a reason for hiding this comment

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

CarGroup 이 move 할 때도 immutable 하게 cars 를 반환하는 것이 좀 더 안전한 프로그램 아닐까 싶은데요~ 🤔
getPositions() 를 호출하기 전까지 CarGroup 이 변경 불가능하다는 것을 보장받는 것이 전 좋아보입니다!

Comment on lines +4 to +10
value class CarName(
val value: String,
) {
init {
require(this.value.isNullOrBlank().not()) { "자동차 이름은 비어 있을 수 없습니다." }
require(this.value.length <= MAX_NAME_LENGTH) { "자동차 이름은 5 글자를 초과할 수 없습니다." }
}
Copy link
Author

Choose a reason for hiding this comment

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

자동차 이름이 비어있을 경우의 유효성 검사도 추가해 보았어요~

Choose a reason for hiding this comment

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

꼼꼼하게 피드백 반영해주셨네요~ 👍

Comment on lines -1 to -5
package racingcar

data class RoundResult(
val carPositions: List<CarPosition>
)
Copy link
Author

@relkimm relkimm Nov 21, 2022

Choose a reason for hiding this comment

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

RoundResult 가 도메인 모델이라기 보다는 화면에 표시하기 위한 dto 느낌이 들어서
RoundResult 대신 CarGroupgetCarPositions 를 활용하는 방향으로 리팩토링해 보았습니다.🙇‍♂️

Comment on lines +1 to +5
package racingcar.view.container

import racingcar.view.component.Component

interface Container : Component
Copy link
Author

Choose a reason for hiding this comment

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

요 친구는 프론트 개발 패턴 중에서 container / presentational 컴포넌트 패턴 을 표현하기 위해 만들어 보았네요!

container 컴포넌트 는 (Container) 로직을 수행하거나 store 에서 데이터를 조회하고
presentational 컴포넌트 에게 데이터를 전달하는 역할을 수행하고 있고

presentational 컴포넌트(Component) 는 단순히 생성자 통해서 전달 받은 데이터를
화면에 표시하는 역할을 수행하도록 만들어 보았네요~!

Copy link

@YooInKeun YooInKeun Nov 22, 2022

Choose a reason for hiding this comment

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

view 영역에 대한 설계를 많이 고민하시고 적용해주셨네요~
프론트 개발 영역은 제가 잘 모르는 부분이라 해당 패턴에 대해서는 코멘트를 드리기 쉽지 않네요.. 😭
자동차 미션의 목적에 좀 더 초점을 맞춰서 리뷰 드리도록 하겠습니다~ 🙏

이때 domain 영역이 view 에 의존하지 않도록 잘 분리해주신 거 같습니다
아쉬웠던 건 각종 InputComponent 가 생성되자마자 onCommand()를 바로 실행해 입력을 받고 있는데
입력을 받도록 명령하는 것은 controller 영역에서 따로 통제하고 controller 를 통해 입력값을 모델에 전달하는 것이 역할 분리가 좀 더 낫지 않을까요?

마찬가지로 ResultComponent 는 controller 에 의해 전달받은 데이터를 그려주는 역할만 하는 것이 적절해보이는데
render() 메소드 안에서 store 에 있는 모델 데이터를 받아오는 역할까지 수행해 여러 책임을 갖는 것으로 보이네요 🤔

현재 구조에서는 controller 의 영역이 따로 없이 view 에서 모델 데이터를 통제하는 역할까지 수행하는 것 같네요~
MVC 패턴을 고민해보면서 관심사의 분리를 시도해보는 것은 어떨까요? 🙇‍♂️

Comment on lines +8 to +14
class RoundContainer(private val round: Round) : Container {
init { this.roundStart() }
private fun roundStart() {
val carGroup = CarGroupStore.getState()
this.round.start(carGroup = carGroup)
CarGroupStore.setState(state = carGroup)
}
Copy link
Author

@relkimm relkimm Nov 21, 2022

Choose a reason for hiding this comment

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

컴포넌트가 마운트 되었을 때의 동작을 어디서 수행하면 좋을까? 고민을 하다가 코틀린의 init 함수를 활용해 보았습니다~

Comment on lines 6 to 12
abstract class Store<T> {
protected val listeners = HashSet<Listener>()

abstract fun getState(): T
abstract fun setState(state: T)
abstract fun subscribe(listener: Listener): UnSubscribe
}
Copy link
Author

Choose a reason for hiding this comment

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

listenerssubscribe 함수를 추가해 보았는데! UI 가 독립적으로 그리고 비동기적으로
랜더링된다면 의미가 있을 것 같은데 지금은 동기적으로 실행되고 있다 보니, 활용을 못 했네요😭😭

Comment on lines 6 to 21
abstract class Store<T> {
protected val listeners = HashSet<Listener>()
open class Store<T>(initialState: T) {
private var state = initialState
private val listeners = HashSet<Listener>()

abstract fun getState(): T
abstract fun setState(state: T)
abstract fun subscribe(listener: Listener): UnSubscribe
fun getState(): T {
return this.state
}
fun setState(state: T) {
this.state = state
this.listeners.forEach { listener -> listener() }
}
fun subscribe(listener: Listener): UnSubscribe {
listeners.add(listener)
return { listeners.remove(listener) }
}
}
Copy link
Author

Choose a reason for hiding this comment

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

전역 상태 관리할 수 있는 store 를 공통으로 사용하기 위해 요렇게 개선해 보았습니다~🙆‍♂️

Copy link

@YooInKeun YooInKeun left a comment

Choose a reason for hiding this comment

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

5단계 미션 수행하시느라 고생 많으셨습니다! 👍
몇가지 코멘트 남겼으니 확인 부탁드려요~
자동차 미션 얼마 남지 않았네요!
마지막까지 화이팅입니다~ 🔥 🔥

Comment on lines +11 to +21
constructor(car: Car) : this(
id = car.id,
name = car.name,
position = car.position,
)

fun move(movePolicy: MovePolicy): Car {
if (movePolicy.canMove()) {
this.position = this.position.forward()
}
return Car(car = this)

Choose a reason for hiding this comment

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

새로운 car 객체를 잘 생성해주신 거 같은데요~
position 프로퍼티를 불변(val)으로 가지고 있어야 진정한 불변 객체가 아닐까 싶습니다! 🤔
현재는 car 객체의 position 값이 var 로 선언되어있네요~

Comment on lines +3 to 8
class CarGroup(private var cars: List<Car>) {
fun move() {
this.cars.forEach { car ->
this.cars = this.cars.map { car ->
val oil = OilStation.generateOilRandomly()
car.move(movePolicy = OilPolicy(oil = oil))
}

Choose a reason for hiding this comment

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

CarGroup 이 move 할 때도 immutable 하게 cars 를 반환하는 것이 좀 더 안전한 프로그램 아닐까 싶은데요~ 🤔
getPositions() 를 호출하기 전까지 CarGroup 이 변경 불가능하다는 것을 보장받는 것이 전 좋아보입니다!

val value: String,
) {
init {
require(this.value.isNullOrBlank().not()) { "자동차 이름은 비어 있을 수 없습니다." }

Choose a reason for hiding this comment

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

마이너하지만 .not() 을 사용하게 되면서 가독성이 약간은 떨어지게 된 거 같아요~
아래와 같이 좀 더 간결하게 작성하는 것도 좋을 거 같습니다!

Suggested change
require(this.value.isNullOrBlank().not()) { "자동차 이름은 비어 있을 수 없습니다." }
require(this.value.isNotBlank()) { "자동차 이름은 비어 있을 수 없습니다." }

Comment on lines +4 to +10
value class CarName(
val value: String,
) {
init {
require(this.value.isNullOrBlank().not()) { "자동차 이름은 비어 있을 수 없습니다." }
require(this.value.length <= MAX_NAME_LENGTH) { "자동차 이름은 5 글자를 초과할 수 없습니다." }
}

Choose a reason for hiding this comment

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

꼼꼼하게 피드백 반영해주셨네요~ 👍

Comment on lines +1 to +5
package racingcar.view.container

import racingcar.view.component.Component

interface Container : Component
Copy link

@YooInKeun YooInKeun Nov 22, 2022

Choose a reason for hiding this comment

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

view 영역에 대한 설계를 많이 고민하시고 적용해주셨네요~
프론트 개발 영역은 제가 잘 모르는 부분이라 해당 패턴에 대해서는 코멘트를 드리기 쉽지 않네요.. 😭
자동차 미션의 목적에 좀 더 초점을 맞춰서 리뷰 드리도록 하겠습니다~ 🙏

이때 domain 영역이 view 에 의존하지 않도록 잘 분리해주신 거 같습니다
아쉬웠던 건 각종 InputComponent 가 생성되자마자 onCommand()를 바로 실행해 입력을 받고 있는데
입력을 받도록 명령하는 것은 controller 영역에서 따로 통제하고 controller 를 통해 입력값을 모델에 전달하는 것이 역할 분리가 좀 더 낫지 않을까요?

마찬가지로 ResultComponent 는 controller 에 의해 전달받은 데이터를 그려주는 역할만 하는 것이 적절해보이는데
render() 메소드 안에서 store 에 있는 모델 데이터를 받아오는 역할까지 수행해 여러 책임을 갖는 것으로 보이네요 🤔

현재 구조에서는 controller 의 영역이 따로 없이 view 에서 모델 데이터를 통제하는 역할까지 수행하는 것 같네요~
MVC 패턴을 고민해보면서 관심사의 분리를 시도해보는 것은 어떨까요? 🙇‍♂️

// when
sut.move(movePolicy = OilPolicy(oil = 충분한_연료))
//
sut.position shouldBe Position(value = 1)
Copy link

@YooInKeun YooInKeun Nov 22, 2022

Choose a reason for hiding this comment

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

car 는 position 값이 변할 수 있는 객체네요~
불변 객체를 만드는 것을 다시 시도해보면 어떨까요?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants