Skip to content
Open
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8c68e8f
feat: 초기 Application 생성
mgim9316-a11y Apr 28, 2026
c64a687
feat: 사다리의 높이 넒이 입력 받는 기능 추가
mgim9316-a11y Apr 28, 2026
8bec14e
feat: 사다리 전체의 정보를 담은 객체 생성
mgim9316-a11y Apr 28, 2026
9ab451e
feat: 랜덤값을 이용한 사다리를 값 설정 기능 추가
mgim9316-a11y Apr 28, 2026
75be806
feat: 사다리의 결과를 출력하는 기능 추가
mgim9316-a11y Apr 28, 2026
5c216cb
feat: 컨트롤러 기능 추가
mgim9316-a11y Apr 28, 2026
4416863
feat: 사다리의 높이를 관리하는 객체 생성
mgim9316-a11y Apr 28, 2026
96cdcde
feat: 사다리의 폭을 관리하는 객체 생성
mgim9316-a11y Apr 28, 2026
05c8532
feat: 사다리 생성하는 클래스 추가
mgim9316-a11y Apr 28, 2026
3813be0
feat: 사다리의 결과를 출력하는 기능 추가
mgim9316-a11y Apr 28, 2026
5f81de0
feat: 사다리의 규칙을 관리하는 기능 추가
mgim9316-a11y Apr 28, 2026
8242c8c
feat: 사다리의 결과를 출력하는 기능 추가
mgim9316-a11y Apr 28, 2026
7e90e30
feat: 사다리의 결과를 계산하는 기능 추가
mgim9316-a11y Apr 28, 2026
cca1c3f
feat: 사다리의 결과를 계산하는 기능 추가
mgim9316-a11y Apr 28, 2026
a12701e
feat: 사다리의 결과를 계산하는 기능 추가
mgim9316-a11y Apr 28, 2026
4444739
feat: 플레이어 객체 생성
mgim9316-a11y Apr 28, 2026
c2f8034
feat: 사다리에 매칭되는 결과 객체 생성
mgim9316-a11y Apr 28, 2026
c4a8b02
feat: 사람을 관리하는 일급 컬렉션 생성
mgim9316-a11y Apr 28, 2026
5644c84
feat: 결과를 관리하는 일급 컬렉션 생성
mgim9316-a11y Apr 28, 2026
160c1bd
feat: 결과와 이름을 저장하는 기능 추가
mgim9316-a11y Apr 28, 2026
821e9d2
feat:
mgim9316-a11y Apr 28, 2026
ea9a9a0
feat:
mgim9316-a11y Apr 28, 2026
3007e43
feat: 사다리 출력 메서드를 printLadderBoard 로 변경. 사다리 출력 전후로 사람 이름과 실행 결과를 6자리…
mgim9316-a11y Apr 28, 2026
9bd9cc1
feat: Map<Position, Position> 에서 객체를 키(Key)로 정상 조회하기 위해 equals 및 hash…
mgim9316-a11y Apr 28, 2026
035a320
feat: 시작 위치를 전달받아 매핑된 도착 위치를 반환하는메서드 추가.
mgim9316-a11y Apr 28, 2026
a38db34
feat: 결과를 출력하는 기능 추가
mgim9316-a11y Apr 28, 2026
b3cf1a4
feat: 사다리 게임 결과를 계산하는 기능 추가
mgim9316-a11y Apr 28, 2026
d03a637
chore: 공백 추가
mgim9316-a11y Apr 28, 2026
cec2e4f
feat: 해시코드, equals 추가
mgim9316-a11y May 1, 2026
b821da1
feat: 객체 비교로 수정
mgim9316-a11y May 1, 2026
ad72977
refactor: 검증 삭제
mgim9316-a11y May 1, 2026
ea59aca
refactor: 이름에 대한 책임 검증 분리
mgim9316-a11y May 1, 2026
895b46b
feat: 이름 입력 형식 검증 기능 추가
mgim9316-a11y May 1, 2026
3b5ff8d
refactor: 변수명 변경
mgim9316-a11y May 1, 2026
d9c9a92
refactor: ladder 의 기능 분리
mgim9316-a11y May 1, 2026
d91171f
refactor: 테스트 기능 수정
mgim9316-a11y May 1, 2026
0eb57b8
refactor: 테스트 기능 수정
mgim9316-a11y May 1, 2026
aebc5ed
refactor: 테스트 기능 수정
mgim9316-a11y May 1, 2026
c7eb9f2
refactor: 테스트 기능 수정
mgim9316-a11y May 1, 2026
f4c7986
feat: 테스트 기능 추가
mgim9316-a11y May 1, 2026
bea7a83
docs: 리드미 파일 작성
mgim9316-a11y May 1, 2026
f6bc97d
chore: 공백 추가
mgim9316-a11y May 1, 2026
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
Empty file added src/README.md
Empty file.
12 changes: 12 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import controller.LadderController;
import view.InputView;
import view.OutputView;

public class Application {
public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
LadderController controller = new LadderController(inputView, outputView);
controller.run();
}
}
56 changes: 56 additions & 0 deletions src/main/java/controller/LadderController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package controller;

import domain.*;
import view.InputView;
import view.OutputView;

public class LadderController {
private final InputView inputView;
private final OutputView outputView;

public LadderController(InputView inputView, OutputView outputView) {
this.inputView = inputView;
this.outputView = outputView;
}

public void run() {
Players players = inputView.readPlayers();
Rewards rewards = inputRewards(players);
LadderHeight height = new LadderHeight(inputView.readHeight());

Ladder ladder = Ladder.generate(new LadderWidth(players.size()), height, new RandomBooleanGenerator());
outputView.printLadderBoard(players, ladder, rewards);

GameResult gameResult = createGameResult(players, rewards, ladder);
printTargetResults(gameResult);
}

private Rewards inputRewards(Players players) {
Rewards rewards = inputView.readRewards();
players.validateMatch(rewards);
return rewards;
}

private GameResult createGameResult(Players players, Rewards rewards, Ladder ladder) {
LadderResult ladderResult = ladder.play(new LadderWidth(players.size()));
return GameResult.of(players, rewards, ladderResult);
}

private void printTargetResults(GameResult gameResult) {
while (true) {
String target = inputView.readTargetPerson();
if (processTarget(target, gameResult)) {
break;
}
}
}

private boolean processTarget(String target, GameResult gameResult) {
if ("all".equals(target)) {
outputView.printAllResults(gameResult);
return true;
}
outputView.printSingleResult(gameResult.findByName(target));
return false;
}
}
6 changes: 6 additions & 0 deletions src/main/java/domain/BooleanGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package domain;


public interface BooleanGenerator {
boolean generate();
}
38 changes: 38 additions & 0 deletions src/main/java/domain/GameResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class GameResult {
private final List<Player> players;

public GameResult(List<Player> players) {
this.players = players;
}

public static GameResult of(Players players, Rewards rewards, LadderResult ladderResult) {
List<Player> playerList = new ArrayList<>();
for (int i = 0; i < players.size(); i++) {
playerList.add(createPlayer(i, players, rewards, ladderResult));
}
return new GameResult(playerList);
}

private static Player createPlayer(int index, Players players, Rewards rewards, LadderResult result) {
Position start = new Position(index);
Position end = result.getEndPosition(start);
return new Player(players.getName(index), rewards.getReward(end.getValue()));
}

public Player findByName(String targetName) {
return players.stream()
.filter(player -> player.hasName(targetName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사람입니다."));
}

public List<Player> getAll() {
return Collections.unmodifiableList(players);
}
}
70 changes: 70 additions & 0 deletions src/main/java/domain/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

public class Ladder {
private final List<Line> lines;

public Ladder(List<Line> lines) {
this.lines = lines;
}

public static Ladder generate(LadderWidth width, LadderHeight height, BooleanGenerator generator) {
Ladder ladder = createLadder(width, height, generator);
while (ladder.hasEmptyInterval(width)) {
ladder = createLadder(width, height, generator);
}
return ladder;
}

private static Ladder createLadder(LadderWidth width, LadderHeight height, BooleanGenerator generator) {
List<Line> lines = new ArrayList<>();
Line currentLine = Line.generateFirst(width, generator);
lines.add(currentLine);
return new Ladder(addRemainingLines(lines, width, height, generator, currentLine));
}

private static List<Line> addRemainingLines(List<Line> lines, LadderWidth width, LadderHeight height, BooleanGenerator generator, Line firstLine) {
Line currentLine = firstLine;
for (int i = 1; i < height.getValue(); i++) {
currentLine = Line.generateNext(width, generator, currentLine);
lines.add(currentLine);
}
return lines;
}

private boolean hasEmptyInterval(LadderWidth width) {
return IntStream.range(0, width.getIntervalCount())
.anyMatch(this::isEmptyInterval);
}

private boolean isEmptyInterval(int index) {
return lines.stream().noneMatch(line -> line.isConnectedAt(index));
}

public LadderResult play(LadderWidth width) {
Map<Position, Position> results = new LinkedHashMap<>();
for (int i = 0; i < width.getValue(); i++) {
Position startPosition = new Position(i);
results.put(startPosition, playOne(startPosition));
}
return new LadderResult(results);
}

private Position playOne(Position startPosition) {
Position current = startPosition;
for (Line line : lines) {
current = line.move(current);
}
return current;
}

public List<Line> getLines() {
return Collections.unmodifiableList(lines);
}
}
24 changes: 24 additions & 0 deletions src/main/java/domain/LadderHeight.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain;

public class LadderHeight {
private static final int MINIMUM_HEIGHT = 1;
private static final String ERROR_MESSAGE = "사다리 높이는 1 이상이어야 합니다.";

private final int value;

public LadderHeight(int value) {
validate(value);
this.value = value;
}

private void validate(int value) {
if (value < MINIMUM_HEIGHT) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
}


public int getValue() {
return value;
}
}
15 changes: 15 additions & 0 deletions src/main/java/domain/LadderResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package domain;
import java.util.Map;


public class LadderResult {
private final Map<Position, Position> results;

public LadderResult(Map<Position, Position> results) {
this.results = results;
}

public Position getEndPosition(Position start) {
return results.get(start);
}
}
28 changes: 28 additions & 0 deletions src/main/java/domain/LadderWidth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package domain;

public class LadderWidth {
private static final int MINIMUM_WIDTH = 2;
private static final String ERROR_MESSAGE = "사다리 폭은 2 이상이어야 합니다.";

private final int value;

public LadderWidth(int value) {
validate(value);
this.value = value;
}

private void validate(int value) {
if (value < MINIMUM_WIDTH) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
}

public int getValue() {
return value;
}

public int getIntervalCount() {
return value - 1;
}

}
78 changes: 78 additions & 0 deletions src/main/java/domain/Line.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Line {
private final List<Boolean> points;

public Line(List<Boolean> points) {
this.points = points;
}

public static Line generateFirst(LadderWidth width, BooleanGenerator generator) {
List<Boolean> points = new ArrayList<>();
boolean previous = false;
for (int i = 0; i < width.getIntervalCount(); i++) {
previous = addPoint(points, previous, false, generator);
}
return new Line(points);
}

public static Line generateNext(LadderWidth width, BooleanGenerator generator, Line previousLine) {
List<Boolean> points = new ArrayList<>();
boolean previous = false;
for (int i = 0; i < width.getIntervalCount(); i++) {
boolean above = previousLine.isConnectedAt(i);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

above = true이면 다음 point가 생기지 않도록 하는 규칙을 넣어주신 것 같아요~
따로 요구사항에는 존재하지 않았던 것 같아서 민욱님이 생각하기에 이 규칙이 필요하다고 판단한 이유가 단순히 궁금했어요.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

극단적인 예시로

|[][][]|-----|[][][][]|
|-----|[][][]|[][][][]|
|-----|[][][]|------|
|-----|[][][]|[][][][]|
|-----|[][][]|[][][][]|

([]는 공백을 의미합니다)

이런 형태의 사다리는 문제가 있는 사다리의 모양이지 않을까? 생각했습니다.
문제라고 생각할 수 있었던 이유는 네이버 사다리 게임을 참고한 결과 같은 위치에 연속으로 가로줄이 배치되는 형태가 발견되지 않음을 확인했기 때문입니다!

따라서 이와같은 이유로 사다리 게임의 규칙을 추가했습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

명시된 요구사항뿐 아니라, 도메인 특성을 직접 파악해서 규칙까지 추가해 주신거군요!
도메인에 대한 이해도가 잘 드러나는 부분이라 좋네요 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

감사합니다 !!

previous = addPoint(points, previous, above, generator);
}
return new Line(points);
}

private static boolean addPoint(List<Boolean> points, boolean previous, boolean above, BooleanGenerator gen) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

L14에서는 BooleanGenerator 의 변수명을 generator로 적어주셨는데, 여기서는 gen으로 축약해서 적어주셨네요. 같은 매게인자인데도 불구하고 네이밍이 다른 이유는 무엇인가요? 축약형은 지양하자가 초반 요구사항이었던 것 같아서요.
그리고 추가로 궁금한 점이 있어요. 축약형은 왜 지양해야 할까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

축약형으로 써서 제출했던 것은 제 부주의였습니다...

축약형 사용을 지양하고 학습 초기부터 잘못된 습관을 바로잡아야 하는 이유는, 시간이 지나면 코드를 작성한 사람이나 읽는 사람 모두 그 의미를 파악하기 어렵기 때문입니다.

여러 개의 변수명이 전부 축약형으로 적혀 있다면 코드를 이해하는 데 불필요한 시간과 노력을 쏟아야 하므로, 학습 초기부터 이런 잘못된 습관을 반드시 바로잡아야 한다고 생각합니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

시간이 지나면 코드를 작성한 사람이나 읽는 사람 모두 그 의미를 파악하기 어렵기 때문입니다.

저랑 같은 생각을 가지고 계시는 군요~

추가적으로 제가 생각하는 다른 이유는
축약형에 대한 컨벤션이 명확히 존재하지 않으면, 각자마다 원하는 축약형 방식으로 네이밍을 작성하고
이를 이해하는 몫은 다른 개발자라고 생각하기 때문도 있어요

boolean nextPoint = determineNext(previous, above, gen);
points.add(nextPoint);
return nextPoint;
}

private static boolean determineNext(boolean previous, boolean above, BooleanGenerator gen) {
if (previous || above) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 변수명을 이렇게 처음 봤을 때는 이전, 위 라는 맥락뿐이어서 이해하는 데 다소 시간이 걸렸던 것 같아요. 좀 더 의미 있는 변수명을 사용해보는 건 어떨까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

네! 맞습니다!

코드는 여러 개의 변수들이 모여서 하나의 큰 흐름을 이룬다고 생각합니다. 그 흐름을 이해하기 위해서는 변수명의 설정이 무엇보다 중요합니다. 만약 변수명이 단순한 영어 단어 하나라면, 그 변수가 의도하는 바를 명확하게 전달하기 어려울 것이라는 점을 깨달았습니다.

앞으로는 단순한 영어 단어의 나열이 아니라, 다른 사람이 코드를 읽었을 때 그 의미를 한 번에 납득할 수 있는 명확한 변수명을 짓도록 의식적으로 노력하겠습니다!

리뷰어님의 소중한 시간을 뺏어가서 죄송합니다...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

제가 남긴 코멘트를 다시 읽어보니,
정답이 정해져 있지 않은 어려운 주제인 만큼 제 의도를 좀 더 명확하게 담았어야 했다는 아쉬움이 남네요.

사실은 민욱님이 어떤 기준으로 네이밍을 하시는지 궁금했던 건데, 정작 그렇게 질문을 드리지는 못했더라고요. 그래도 스터디 자리에서나마 이에 대한 생각을 들어볼 수 있어서 좋았습니다!

민기님 말씀처럼,
저도 민기님도 여전히 고민하고 있는 부분이고,
'명확한 네이밍'에 대한 기준은 사람마다 다를 수밖에 없는 것 같아요.
그래서 자기만의 기준이 잘 서 있다면 어떤 상황에서도 흔들리지 않고 풀어갈 수 있을 거라 생각합니다!

return false;
}
return gen.generate();
}

public boolean isConnectedAt(int index) {
return points.get(index);
}

public Position move(Position position) {
int currentIndex = position.getValue();
if (canMoveLeft(currentIndex)) {
return position.moveLeft();
}
if (canMoveRight(currentIndex)) {
return position.moveRight();
}
return position;
}

private boolean canMoveLeft(int currentIndex) {
if (currentIndex <= 0) {
return false;
}
return points.get(currentIndex - 1);
}

private boolean canMoveRight(int currentIndex) {
if (currentIndex >= points.size()) {
return false;
}
return points.get(currentIndex);
}

public List<Boolean> getPoints() {
return Collections.unmodifiableList(points);
}
}
24 changes: 24 additions & 0 deletions src/main/java/domain/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain;


public class Player {
private final PlayerName name;
private final Reward reward;

public Player(PlayerName name, Reward reward) {
this.name = name;
this.reward = reward;
}

public boolean hasName(String targetName) {
return this.name.getValue().equals(targetName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

getter로 객체 내부의 값을 꺼내서 Player가 직접 비교하고 있는데요,
PlayerName 입장에서 보면 외부인 Player가 자신의 값을 꺼내 비교하는 셈이 되지 않을까요?
혹시 민욱님은 이 부분에 대해 어떻게 생각하시나요!?

@hapdaypy hapdaypy Apr 30, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

처음 저는 Player가 PlayerName의 값을 꺼내와서 자바에서 제공하는 equals API로 비교해도 문제가 없다고 생각했습니다.

그동안 객체 내부의 값을 반환하는 것을 지양해야 하는 주된 이유가 '외부에서 값을 수정할 위험' 때문이라고 생각했는데, 안전한 자바 API와 불변 객체를 사용한다면 그 위험을 피할 수 있다고 판단했기 때문입니다. 하지만 핵심은 값의 변질 여부가 아니라, 객체의 책임과 자율성에 관한 문제였습니다!

리뷰어님의 말씀대로 Player라는 외부 객체가 PlayerName 내부의 값을 직접 꺼내어 비교하는 것은 객체의 자율성을 지키지 못한 방식이라고 생각합니다.

이를 리팩토링한다면, Player는 비교를 요청하기만 하고 직접적인 비교 로직은 값을 가지고 있는 PlayerName 스스로가 처리하는 방식으로 리팩토링을 진행할 수 있겠습니다 !

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

오 좋습니다~~ 맞아요 제가 이야기 하고 싶었던 부분은 객체의 책임에 대한 이야기였습니다.

Player는 비교를 요청하기만 하고 직접적인 비교 로직은 값을 가지고 있는 PlayerName 스스로가 처리하는 방식

이렇게도 풀 수 있겠네요~

그럼 제가 처음에 코멘트를 남긴 방향성부터 말씀드려볼게요!객체의 자율성 이야기가 조금 추상적으로 느껴질 수 있을 것 같아서, 좀 더 현실적인 이유를 들어볼게요. 저는 PlayerName이라는 값 객체를 만든 이유 자체가, "이름"이라는 개념에 단순한 문자열 이상의 규칙이 붙을 수 있기 때문이라고 생각해요. 예를 들면 이런 상황들이요.

  • 대소문자 구분 안 함
  • 앞 뒤 공백 무시
  • 등등

그래서 지금은 비교가 equals로 끝나지만, 나중에 "공백을 무시하고 비교해줘"라는 요구사항이 생겼다고 가정을 해볼게요!
getter 방식이라면

this.name.getValue().equals(targetName)

이 코드를 쓰는 모든 곳에서 .trim()을 붙이려고 수정을 해야 하는 일이 생길거에요!
만약 비교 메서드가 PlayerName 내에 존재한다면:

this.name.equals(new PlayerName(targetName))
// 또는
this.name.matches(targetName)

규칙이 바뀌더라도 PlayerName 안의 메서드 하나만 고치면 될 거에요!

가정한 시나리오가 그렇게 현실적이지 않을 수도 있겠지만, 결국 PlayerName이라는 값 객체를 만든 이유는 "이름은 그냥 String이 아니라 자기만의 규칙을 가진 개념이다"라고 선언하고 싶은 거라고 생각해요. 근데 비교할 때마다 getValue()로 String을 꺼내버리면, 객체에 데이터만 있고 행동은 없는 상태가 되는 것 같아요.


[추가 리펙토링 방향]

이미 민욱님이 충분히 같은 방향성으로 이해해주셨다고 생각해요!
다만 조금 욕심내서 리팩터링 방향성에 대한 코멘트를 남겨본다면, 지금 비교 방식이

new PlayerName(targetName)

라는 것을 확인했어요!

지금 확인하고 싶은게 이름이 같은 지 아닌 지 단순 boolean 값일 건데, 만일 생성자에 있는 검증 로직을 돌 수 도 있겠다는 생각이 들더라고요.
그러면 false라는 반환값이 아니라 예외가 터질 수도 있고요!
또한, PlayerName이라는 값 객체가 현재 하나의 필드만을 요하고 있는데, 값 객체를 생성하고자 하는 상황에서 추가적인 필드도 필요한 상황이 있다면 이 비교 메서드도 수정해야 하는 상황이 올 거라는 생각이 들었어요.

상황을 예로 들어보면 언어 정보도 필요하다고 가정을 해볼게요!
PlayerName(name, locale) 이렇게 될 거에요.

그러면 이름 문자열이 같은지만 묻고 싶었던 건데, locale 정보까지 신경 써야 하는 상황이 와요. 비교 행위가 PlayerName의 내부 구조에 종속되어 버리는 거죠.

반면 PlayerName 안에 비교 메서드를 두면:

// PlayerName 내부
public boolean matches(String input) {
    return this.value.equals(input);  // 내부 사정은 PlayerName이 알게끔
}

필드가 늘어나도 외부 코드는 영향을 받지 않을 거라는 생각이 들었습니다!!

}

public PlayerName getName() {
return name;
}

public Reward getReward() {
return reward;
}
}
17 changes: 17 additions & 0 deletions src/main/java/domain/PlayerName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package domain;

public class PlayerName {
private static final int MAX_LENGTH = 5;
private final String value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Position에서는 equals / hashcode가 잘 구현되어 있음을 확인했어요! 다만 이 클래스, PlayerName에는 equals / hashcode가 구현되어 있지 않네요~ 둘의 설계 방식이 다른데 왜 그렇게 다르게 구현했는 지 설명해주실 수 있나요? 만일 단순 빼먹은 거라면 equals / hashcode가 없는 PlayerName에서 어떠한 상황이 초래할 수 있기 때문에 구현해야 하는 걸까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

제가 해당 코드 작성을 누락했습니다...
아무래도 생소한 개념이다 보니 누락해서 제출했지만 다음 미션부터는 꼼꼼하게 작성해보도록 하겠습니다 ! ...

PlayerName 에서 equals / hashcode 를 구현해야하는 이유는 다음과 같습니다!

  1. 동일성과 동등성의 분리

동일성 : 자바에서 new PlayerName("이름")를 두 번 실행하면 메모리상에 완전히 독립된 2개의 객체가 생성됩니다. 자바의 기본 equals()는 이 메모리 주소를 비교하므로 둘을 다르다고 판별합니다. => 같은 이름을 가진 서로 다른 사람 : 동명이인

동등성 : 사다리 게임 도메인에서는 이름표에 적힌 글자(value)가 같다면 같은 사람으로 취급해야 합니다. 메모리 주소가 다르더라도 내부 값이 같으면 true를 반환하도록 논리적 동등성을 부여하는 작업이 equals() 재정의입니다. => 한 사람

  1. hashCode의 동반 필수성

equals만 재정의하고 hashCode를 재정의하지 않으면, HashSet이나 HashMap 같은 해시 기반 자료구조를 사용할 때 중복 검증이나 데이터 검색을 실패하게 됩니다. 메모리 주소 기반의 엉뚱한 해시코드를 반환하기 때문입니다.

그렇다면 equals / hashcode 를 사용하지 않았다면 생길 수 있는 문제점은 다음과 같습니다!

  1. 동명이인 참가자의 허용
    까지만 알겠고 더 다른 내용이 있지만 완벽하게 이해가 되지 않아서 쫌 더 연구해보고 다시 코멘트를 남기겠습니다 !

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

equals / hashcode 구현 이유의 두가지를 정확하게 짚어주셨네요

동명이인 참가자의 허용

제가 생각하는 방향성도 이와 동일합니다~

  • equals / hashcode 를 구현하지 않으면 동명이인 참가자를 허용하는 시나리오
  • equals / hashcode 를 구현하면 동명이인 참가자를 허용하지 않는 시나리오

그래서 민욱님이 어떤 시나리오를 염두에 두셨는지에 따라, 이게 코드 작성의 누락이 아닐 수도 있어요.

제가 코멘트를 단 이유는, 구현하지 않은 설계에 의도가 있었던 것인지 아닌지가 궁금해서였어요. equals / hashCode 구현 여부를 스스로의 기준에 따라 고민하고 설계할 수 있는지를 고민해볼 수 있는 미션이라 생각했기 때문이에요~

또한 이 부분은 스터디 시간에도 추가로 질문해주신 덕분에 한 번 더 질답이 오갈 수 있었고, 저도 민욱님이 어디서 헷갈리시는지 알 수 있어서 좋았어요 ㅋㅎㅋㅎ 저도 똑같이 헷갈렸던 지점이라 참 반가웠습니다.
앞으로도 더더 질문해주세요~

까지만 알겠고 더 다른 내용이 있지만 완벽하게 이해가 되지 않아서 쫌 더 연구해보고 다시 코멘트를 남기겠습니다 !

또 어떤 부분이 있을랑가요~~
만일 찾으면 저도 알려주세요 ㅋㅋㅋ 아니라면 꼭 다시 안 다셔도 되어요~ ㅎ.ㅎ


public PlayerName(String value) {
if (value.length() > MAX_LENGTH) {
throw new IllegalArgumentException("이름은 5자를 초과할 수 없습니다.");
}
this.value = value;
}

public String getValue() {
return value;
}
}
Loading