-
Notifications
You must be signed in to change notification settings - Fork 50
[사다리] 이상현 미션 제출합니다. #60
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: idealhyun
Are you sure you want to change the base?
Changes from all commits
ac87e79
08bc635
dde3d18
55117ac
ebdfa32
c4f828a
5331839
a552956
c33eba6
b6cb255
21401af
54ede43
911f89f
3ebbcd6
c834d5f
4808f0e
9397039
d4253eb
24e2c1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# 사다리 - 함수형 프로그래밍 | ||
|
||
## 1단계 요구 사항 | ||
|
||
- 모든 엔티티를 작게 유지한다. | ||
- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. | ||
- 사다리게임에 설정 값(높이, 너비)를 넘겨준다. | ||
- inputView는 사다리 게임 상태를 받아서 출력한다. | ||
|
||
## 2단계 요구 사항 | ||
|
||
- 사다리의 넓이와 높이를 입력받아서 사다리를 생성하게 한다. | ||
|
||
## 3단계 요구사항 | ||
|
||
- 사다리 타기 후 결과를 출력해야한다. | ||
- 각 시작점마다 어디에 도착하는지 출력한다. | ||
|
||
## 4단계 요구사항 | ||
|
||
- 사다리 게임에 참여하는 사람에 이름을 최대 5글자까지 부여할 수 있다. | ||
- 사다리를 출력할 때 사람 이름도 같이 출력한다. | ||
- 사람 이름은 쉼표(,)를 기준으로 구분한다. | ||
- 개인별 이름을 입력하면 개인별 결과를 출력하고, "all"을 입력하면 전체 참여자의 실행 결과를 출력한다. | ||
|
||
## 프로그래밍 요구사항 | ||
|
||
- 자바 코드 컨벤션을 지키면서 프로그래밍한다. | ||
- 기본적으로 Java Style Guide을 원칙으로 한다. | ||
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다. | ||
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. | ||
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. | ||
- 3항 연산자를 쓰지 않는다. | ||
- else 예약어를 쓰지 않는다. | ||
- 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. | ||
- 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다. | ||
- 배열 대신 컬렉션을 사용한다. | ||
- 줄여 쓰지 않는다(축약 금지). | ||
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다. | ||
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. | ||
- 모든 원시 값과 문자열을 포장한다. | ||
- 일급 컬렉션을 쓴다. | ||
- Java Enum을 적용한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import config.GameConfig; | ||
import domain.LadderGame; | ||
import domain.LadderResult; | ||
import domain.Player; | ||
import domain.Players; | ||
import domain.Reward; | ||
import domain.Rewards; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import view.InputView; | ||
import view.OutputView; | ||
|
||
public class LadderGameApp { | ||
public static void main(String[] args) { | ||
OutputView outputView = new OutputView(); | ||
InputView inputView = new InputView(); | ||
Players players = getPlayers(inputView); | ||
Rewards rewards = getRewards(inputView); | ||
GameConfig gameConfig = getGameConfig(inputView, players.size()); | ||
|
||
LadderGame ladderGame = LadderGame.of(gameConfig); | ||
LadderResult ladderResult = ladderGame.play(); | ||
outputView.printLadderState(ladderGame.getLadder(), players, rewards); | ||
printTargetResult(inputView, outputView, ladderResult, players, rewards); | ||
} | ||
|
||
private static void printTargetResult(InputView inputView, OutputView outputView, LadderResult ladderResult, | ||
Players players, Rewards rewards) { | ||
String targetPlayerName = inputView.getTargetPlayerName(); | ||
if (Objects.equals(targetPlayerName, "all")) { | ||
outputView.printLadderResult(ladderResult, players, rewards); | ||
return; | ||
} | ||
|
||
outputView.printTargetReward(targetPlayerName, ladderResult, players, rewards); | ||
} | ||
|
||
private static Players getPlayers(InputView inputView) { | ||
List<String> names = inputView.getPlayers(); | ||
List<Player> playerList = names.stream() | ||
.map(Player::new) | ||
.toList(); | ||
return new Players(playerList); | ||
} | ||
|
||
private static Rewards getRewards(InputView inputView) { | ||
List<String> rewards = inputView.getRewards(); | ||
List<Reward> rewardList = rewards.stream() | ||
.map(Reward::new) | ||
.toList(); | ||
return new Rewards(rewardList); | ||
} | ||
|
||
private static GameConfig getGameConfig(InputView inputView, int width) { | ||
final int height = inputView.getHeight(); | ||
return new GameConfig(width, height); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package config; | ||
|
||
public class GameConfig { | ||
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. 얘는 별도의 패키지인 config로 분리하였군요. 분리하신 이유는 어떤건가요? 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. 카카오톡의 사다리타기처럼 '꽝뽑기', '간식내기' 등등 이런 카테고리나 다른 설정 값들이 나중에 추가될수도 있지 않을까? 라는 생각에서 분리하려고 했던 것 같습니다. 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. 확장성 때문에 별도로 분리한 것이군요! domain이라면 domain 패키지 밖에 두신 이유가 있나요? |
||
private final int width; | ||
private final int height; | ||
|
||
public GameConfig(int width, int height) { | ||
this.width = width; | ||
this.height = height; | ||
} | ||
|
||
public int getWidth() { | ||
return width; | ||
} | ||
|
||
public int getHeight() { | ||
return height; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package domain; | ||
|
||
public record Connection(boolean connectedRight) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package domain; | ||
|
||
import java.util.List; | ||
|
||
public class Ladder { | ||
private final List<Line> lines; | ||
|
||
public Ladder(List<Line> lines) { | ||
this.lines = List.copyOf(lines); | ||
} | ||
|
||
public List<Line> getLines() { | ||
return lines; | ||
} | ||
|
||
public int move(int startIndex) { | ||
int position = startIndex; | ||
for (Line line : lines) { | ||
position = line.move(position); | ||
} | ||
return position; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package domain; | ||
|
||
import config.GameConfig; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
|
||
public class LadderGame { | ||
private final GameConfig config; | ||
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. GameConfig의 경우에는 한번 쓰고 이후 ladderGame에서 안 쓰이는 것 같아요. 필드에서 빼보는 건 어떨까요? |
||
private final Ladder ladder; | ||
|
||
private LadderGame(GameConfig config, Ladder ladder) { | ||
this.config = config; | ||
this.ladder = ladder; | ||
} | ||
|
||
public static LadderGame of(GameConfig config) { | ||
Ladder ladder = new LadderGenerator().generate(config); | ||
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. LadderGenerator의 경우에는 단순히 Ladder를 생성만 하고 있습니다. LadderGenerator 객체를 매번 생성하기보다 static 으로 정의해서 사용해보는 건 어떨까요?? |
||
return new LadderGame(config, ladder); | ||
} | ||
|
||
public LadderResult play() { | ||
Map<Integer, Integer> resultMap = new LinkedHashMap<>(); | ||
for (int i = 0; i < config.getWidth(); i++) { | ||
int destination = ladder.move(i); | ||
resultMap.put(i, destination); | ||
} | ||
return new LadderResult(resultMap); | ||
} | ||
|
||
public Ladder getLadder() { | ||
return ladder; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package domain; | ||
|
||
import config.GameConfig; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Random; | ||
|
||
public class LadderGenerator { | ||
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. 별도로 Generator를 분리한 게 인상적이네요. Ladder 내부적으로 정적팩터리 메서드를 쓸수도 있는데 분리하신 이유가 있을까요? |
||
private final Random random = new Random(); | ||
|
||
public Ladder generate(GameConfig config) { | ||
List<Line> lines = new ArrayList<>(); | ||
for (int i = 0; i < config.getHeight(); i++) { | ||
lines.add(generateLine(config.getWidth())); | ||
} | ||
return new Ladder(lines); | ||
} | ||
|
||
private Line generateLine(int width) { | ||
List<Connection> connections = new ArrayList<>(); | ||
boolean prev = false; | ||
for (int i = 0; i < width - 1; i++) { | ||
boolean connect = !prev && random.nextBoolean(); | ||
connections.add(new Connection(connect)); | ||
prev = connect; | ||
} | ||
return new Line(connections); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package domain; | ||
|
||
import java.util.Collections; | ||
import java.util.Map; | ||
|
||
public class LadderResult { | ||
private final Map<Integer, Integer> resultMap; | ||
|
||
public LadderResult(Map<Integer, Integer> resultMap) { | ||
this.resultMap = resultMap; | ||
} | ||
|
||
public Map<Integer, Integer> getResultMap() { | ||
return Collections.unmodifiableMap(resultMap); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package domain; | ||
|
||
import java.util.List; | ||
|
||
public class Line { | ||
private final List<Connection> connections; | ||
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. 이름이 직관적이어서 보기 편하네요. connection에서 connectRight가 읽기 좋았어요 |
||
|
||
public Line(List<Connection> connections) { | ||
validate(connections); | ||
this.connections = connections; | ||
} | ||
|
||
private void validate(List<Connection> connections) { | ||
for (int i = 0; i < connections.size() - 1; i++) { | ||
boolean curr = connections.get(i).connectedRight(); | ||
boolean next = connections.get(i + 1).connectedRight(); | ||
if (curr && next) { | ||
throw new IllegalArgumentException("사다리 가로선에 연속된 연결선이 존재합니다."); | ||
} | ||
} | ||
} | ||
|
||
public int move(int index) { | ||
if (canMoveRight(index)) { | ||
return index + 1; | ||
} | ||
if (canMoveLeft(index)) { | ||
return index - 1; | ||
} | ||
return index; | ||
} | ||
|
||
private boolean canMoveRight(int index) { | ||
return index < connections.size() && connections.get(index).connectedRight(); | ||
} | ||
|
||
private boolean canMoveLeft(int index) { | ||
return index > 0 && connections.get(index - 1).connectedRight(); | ||
} | ||
|
||
public List<Connection> getConnections() { | ||
return List.copyOf(connections); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package domain; | ||
|
||
public record Player(String name) { | ||
private static final int MAX_NAME_LENGTH = 5; | ||
|
||
public Player { | ||
validate(name); | ||
} | ||
|
||
private void validate(String name) { | ||
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. 만약 player의 이름으로 all이 입력되면 어떻게 될까요? 이런 예약어들을 어떻게 관리하 수 있을까요? |
||
if (name.length() > MAX_NAME_LENGTH) { | ||
throw new IllegalArgumentException("이름은 5자를 넘어갈 수 없습니다."); | ||
} | ||
if (name.isBlank()) { | ||
throw new IllegalArgumentException("이름에 공백이 들어왔습니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package domain; | ||
|
||
import java.util.List; | ||
|
||
public class Players { | ||
private final List<Player> players; | ||
|
||
public Players(List<Player> players) { | ||
validate(players); | ||
this.players = players; | ||
} | ||
|
||
private void validate(List<Player> players) { | ||
if (players == null || players.isEmpty()) { | ||
throw new IllegalArgumentException("플레이어 목록은 비어 있을 수 없습니다."); | ||
} | ||
} | ||
|
||
public List<Player> getPlayers() { | ||
return List.copyOf(players); | ||
} | ||
|
||
public int size() { | ||
return players.size(); | ||
} | ||
|
||
public Player get(int index) { | ||
return players.get(index); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package domain; | ||
|
||
public record Reward(String name) { | ||
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. record와 class의 차이는 뭔가요? 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. record는 불변하는 데이터에 대한 표현에 목적을 두고 있고, |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package domain; | ||
|
||
import java.util.List; | ||
|
||
public class Rewards { | ||
private final List<Reward> rewards; | ||
|
||
public Rewards(List<Reward> rewards) { | ||
validate(rewards); | ||
this.rewards = rewards; | ||
} | ||
|
||
private void validate(List<Reward> rewards) { | ||
if (rewards == null || rewards.isEmpty()) { | ||
throw new IllegalArgumentException("보상 목록은 비어 있을 수 없습니다."); | ||
} | ||
} | ||
|
||
public List<Reward> getRewards() { | ||
return List.copyOf(rewards); | ||
} | ||
|
||
public int size() { | ||
return rewards.size(); | ||
} | ||
|
||
public Reward get(int index) { | ||
return rewards.get(index); | ||
} | ||
} |
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.
이 출력에 대한 분기도 흐름을 제어하기 보다, 입력에 따라서 출력이 달라지는 부분 같아요.
outputView 쪽에 둔게 아니라 LadderGameApp에 둔 이유가 있을까요?