-
Notifications
You must be signed in to change notification settings - Fork 251
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
[1단계 - 사다리 생성] 페드로(류형욱) 미션 제출합니다 #286
Changes from all commits
c41161d
a12491d
1e98f6e
68009aa
3f5c32f
acdc850
36fe94d
08955b2
02394eb
f24d5f2
9035f45
8894f33
f2a6ebd
c3db653
ab0e879
7a15b28
4a32498
02a9cba
6a0179f
57e7d3e
db70eab
989beb3
4749c0f
9229cc5
8100bf3
88cbcff
1e77b7b
2559203
bf82fec
0b81f89
615a256
ebc2132
1740b58
cfed9fa
612c913
c8d4463
0924132
26a1203
424085b
434aa96
1b67f2a
0cb3c19
684c0c9
4cfd531
4d592b4
79907af
d0f7e3c
42cd6de
3946292
e916985
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 |
---|---|---|
|
@@ -30,3 +30,5 @@ out/ | |
|
||
### VS Code ### | ||
.vscode/ | ||
|
||
.gitmessage.txt |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
## 주요 기능 | ||
|
||
1. 참여할 사람 이름 입력 | ||
2. 사다리 높이 입력 | ||
3. 이름과 높이로 사다리 생성 | ||
4. 실행 결과 출력 | ||
|
||
### 참여할 사람 이름 입력 | ||
- [x] 이름 입력 안내 문구 출력 | ||
- [x] 사용자로부터 이름을 입력받는 기능 | ||
- [x] `,` 를 기준으로 이름을 분할하는 기능 | ||
- [x] 중복된 이름이 존재하는지 확인하는 기능 | ||
|
||
### 사다리 높이 입력 | ||
- [x] 높이 입력 안내 문구 출력 | ||
- [x] 사용자로부터 사다리 높이를 입력받는 기능 | ||
|
||
### 이름과 높이로 사다리 생성 | ||
- [x] 지정된 높이와 폭의 사다리를 생성하는 기능 | ||
- [x] 특정 위치에서 경로의 연결 여부를 랜덤하게 선택하는 기능 | ||
- [x] 생성된 유효한 가로줄로 사다리를 구성하는 기능 | ||
|
||
### 가로줄 생성 | ||
- [x] 유효한 가로줄인지 검증하는 기능 | ||
- 한 가로줄 내에 연속된 경로가 없어야 한다. | ||
- 오른쪽 경로 오른쪽에 항상 왼쪽 경로가 있어야 한다. | ||
- 왼쪽 경로 왼쪽에 항상 오른쪽 경로가 있어야 한다. | ||
|
||
### 실행 결과 출력 | ||
|
||
- [x] “실행 결과” 문구 출력 | ||
- [x] 참여자 이름 출력 | ||
- [x] 사다리 출력 | ||
|
||
### 예외 처리 | ||
|
||
- [x] 유효한 참여자 이름이 아닐 경우 예외 발생 | ||
- 이름의 길이는 1 이상 5이하여야 함 | ||
- 이름에 포함되는 문자는 영문자와 숫자만 허용함 | ||
- [x] 유효한 사다리 높이가 아닐 경우 예외 발생 | ||
- 높이는 항상 자연수여야 함 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package ladder; | ||
|
||
import ladder.controller.LadderController; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
LadderController ladderController = new LadderController(); | ||
ladderController.start(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package ladder.controller; | ||
|
||
import ladder.model.Ladder; | ||
import ladder.model.Players; | ||
import ladder.view.InputView; | ||
import ladder.view.OutputView; | ||
|
||
import java.util.List; | ||
|
||
public class LadderController { | ||
private Players ladderPlayers; | ||
private Ladder ladder; | ||
|
||
public void start() { | ||
init(); | ||
printResult(); | ||
} | ||
|
||
private void init() { | ||
ladderPlayers = Players.from(readPlayerNames()); | ||
|
||
int height = readLadderHeight(); | ||
int width = ladderPlayers.getSize(); | ||
ladder = Ladder.of(height, width); | ||
} | ||
|
||
private List<String> readPlayerNames() { | ||
return InputView.inputPlayerNames(); | ||
} | ||
|
||
private int readLadderHeight() { | ||
return InputView.inputLadderHeight(); | ||
} | ||
|
||
private void printResult() { | ||
OutputView.printResultDescription(); | ||
OutputView.printPlayerNames(ladderPlayers.getPlayerNames()); | ||
OutputView.printLadder(ladder.toLineDtoList()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package ladder.dto; | ||
|
||
import ladder.model.Line; | ||
|
||
import java.util.List; | ||
|
||
public class LineDto { | ||
private final List<Boolean> connected; | ||
|
||
private LineDto(List<Boolean> connected) { | ||
this.connected = connected; | ||
} | ||
|
||
public static LineDto from(Line line) { | ||
return new LineDto(line.getConnected()); | ||
} | ||
|
||
public List<Boolean> getConnected() { | ||
return connected; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package ladder.model; | ||
|
||
import java.util.stream.IntStream; | ||
import ladder.dto.LineDto; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Random; | ||
|
||
public class Ladder { | ||
private static final Random random = new Random(); | ||
private final List<Line> ladder; | ||
|
||
private Ladder(List<Line> ladder) { | ||
this.ladder = ladder; | ||
} | ||
|
||
public static Ladder of(int height, int width) { | ||
validate(height, width); | ||
|
||
List<Line> ladder = IntStream.range(0, height) | ||
.mapToObj(idx -> new Line(makeRandomRow(width))) | ||
.toList(); | ||
|
||
return new Ladder(ladder); | ||
} | ||
|
||
private static void validate(int height, int width) { | ||
if (notNaturalNumber(height) || notNaturalNumber(width)) { | ||
throw new IllegalArgumentException("사다리 높이와 너비는 자연수여야 합니다."); | ||
} | ||
} | ||
|
||
private static boolean notNaturalNumber(int value) { | ||
return value <= 0; | ||
} | ||
|
||
private static List<LadderPath> makeRandomRow(int width) { | ||
List<LadderPath> randomPath = new ArrayList<>(generatePairableRandomPath(width)); | ||
|
||
if (randomPath.size() < width) { | ||
randomPath.add(LadderPath.STAY); | ||
} | ||
|
||
return randomPath; | ||
} | ||
|
||
private static List<LadderPath> generatePairableRandomPath(int maxWidth) { | ||
List<LadderPath> randomPathWithPair = new ArrayList<>(); | ||
|
||
while (randomPathWithPair.size() < maxWidth - 1) { | ||
randomPathWithPair.addAll(generateRandomPath()); | ||
} | ||
|
||
return randomPathWithPair; | ||
} | ||
|
||
private static List<LadderPath> generateRandomPath() { | ||
boolean isConnectedPath = random.nextBoolean(); | ||
|
||
if (isConnectedPath) { | ||
return List.of(LadderPath.RIGHT, LadderPath.LEFT); | ||
} | ||
return List.of(LadderPath.STAY); | ||
} | ||
|
||
public List<LineDto> toLineDtoList() { | ||
return ladder.stream() | ||
.map(LineDto::from) | ||
.toList(); | ||
} | ||
Comment on lines
+67
to
+71
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. 반영했습니다! 이 변경과 관련하여 궁금한 점이 생겼는데, 2단계 PR때 정리해서 질문드릴게요! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package ladder.model; | ||
|
||
public enum LadderPath { | ||
STAY, LEFT, RIGHT; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package ladder.model; | ||
|
||
import java.util.List; | ||
import java.util.stream.IntStream; | ||
|
||
import static ladder.model.LadderPath.*; | ||
|
||
public class Line { | ||
private final List<LadderPath> row; | ||
|
||
public Line(List<LadderPath> row) { | ||
validate(row); | ||
this.row = row; | ||
} | ||
|
||
private void validate(List<LadderPath> row) { | ||
if (isLeftOnFirst(row) || isRightOnEnd(row)) { | ||
throw new IllegalArgumentException("유효한 가로줄이 아닙니다."); | ||
} | ||
if (!isLeftAlwaysExistAfterRight(row) || !isRightAlwaysExistBeforeLeft(row)) { | ||
throw new IllegalArgumentException("유효한 가로줄이 아닙니다."); | ||
} | ||
} | ||
|
||
private boolean isLeftOnFirst(List<LadderPath> row) { | ||
return row.get(0) == LEFT; | ||
} | ||
|
||
private boolean isRightOnEnd(List<LadderPath> row) { | ||
return row.get(row.size() - 1) == RIGHT; | ||
} | ||
|
||
private boolean isLeftAlwaysExistAfterRight(List<LadderPath> row) { | ||
return IntStream.range(0, row.size()) | ||
.filter(idx -> row.get(idx).equals(RIGHT)) | ||
.map(idx -> idx + 1) | ||
.allMatch(idx -> row.get(idx).equals(LEFT)); | ||
} | ||
|
||
private boolean isRightAlwaysExistBeforeLeft(List<LadderPath> row) { | ||
return IntStream.range(0, row.size()) | ||
.filter(idx -> row.get(idx).equals(LEFT)) | ||
.map(idx -> idx - 1) | ||
.allMatch(idx -> row.get(idx).equals(RIGHT)); | ||
} | ||
|
||
public int size() { | ||
return row.size(); | ||
} | ||
|
||
public List<Boolean> getConnected() { | ||
return IntStream.range(0, row.size() - 1) | ||
.mapToObj(idx -> row.get(idx).equals(RIGHT)) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package ladder.model; | ||
|
||
public class Player { | ||
private static final int MAX_NAME_LENGTH = 5; | ||
private static final String NAME_PATTERN = "^[a-zA-Z0-9]*$"; | ||
|
||
private final String name; | ||
|
||
public Player(String name) { | ||
validate(name); | ||
this.name = name; | ||
} | ||
|
||
private void validate(String name) { | ||
if (isNameLengthLongerThanMaxLength(name)) { | ||
throw new IllegalArgumentException("이름의 길이는 5를 초과할 수 없다."); | ||
} | ||
if (isNameEmpty(name)) { | ||
throw new IllegalArgumentException("이름이 비어 있습니다."); | ||
} | ||
if (!isNameFormatValid(name)) { | ||
throw new IllegalArgumentException("이름은 영문자와 숫자로 구성되어야 합니다."); | ||
} | ||
} | ||
|
||
private boolean isNameLengthLongerThanMaxLength(String name) { | ||
return name.length() > MAX_NAME_LENGTH; | ||
} | ||
|
||
private boolean isNameEmpty(String name) { | ||
return name.isEmpty(); | ||
} | ||
|
||
private boolean isNameFormatValid(String name) { | ||
return name.matches(NAME_PATTERN); | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package ladder.model; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
|
||
public class Players { | ||
private final List<Player> players; | ||
|
||
private Players(List<Player> players) { | ||
this.players = players; | ||
} | ||
|
||
public static Players from(List<String> playerNames) { | ||
validate(playerNames); | ||
return new Players(playerNames.stream() | ||
.map(Player::new) | ||
.toList() | ||
); | ||
} | ||
Comment on lines
+13
to
+19
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. Q) 정적팩토리 메서드에서 메서드 분리를 통해 한 번에 하나의 조건만 검증하도록 했더니 static 메서드가 너무 많아지는 문제가 있었습니다. 정상이에요 괜찮아요. 이 정도 static 으로 문제가 발생할정도로 서버가 나약하지 않아요 :) 만약 그럼에도 static 이 늘어나는걸 원하지 않으신다면, 이름을 Player 로 바꾸는 과정을 외부에서 진행하고 생성자를 통해서 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. 찝찝했던 부분이 시원하게 해결되었습니다!ㅎㅎ 설계는 트레이드오프 활동이라는게 정말 맞는 것 같아요. |
||
|
||
private static void validate(List<String> playerNames) { | ||
if (isDuplicated(playerNames)) { | ||
throw new IllegalArgumentException("중복되는 이름이 존재합니다."); | ||
} | ||
} | ||
|
||
private static boolean isDuplicated(List<String> playerNames) { | ||
return new HashSet<>(playerNames).size() != playerNames.size(); | ||
} | ||
|
||
public int getSize() { | ||
return players.size(); | ||
} | ||
|
||
public List<String> getPlayerNames() { | ||
return players.stream() | ||
.map(Player::getName) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package ladder.utils; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
|
||
public class ConsoleReader { | ||
private static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); | ||
|
||
private ConsoleReader() { | ||
} | ||
|
||
public static String readLine() { | ||
try { | ||
return br.readLine(); | ||
} catch (IOException e) { | ||
throw new IllegalArgumentException("입력 받는 중 예외가 발생했습니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package ladder.view; | ||
|
||
import java.util.List; | ||
import ladder.utils.ConsoleReader; | ||
|
||
public class InputView { | ||
private static final String INPUT_NAME_DESCRIPTION = "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"; | ||
private static final String INPUT_HEIGHT_DESCRIPTION = "최대 사다리 높이는 몇 개인가요?"; | ||
private static final String NAME_DELIMITER = ","; | ||
|
||
private InputView() { | ||
} | ||
|
||
public static List<String> inputPlayerNames() { | ||
System.out.println(INPUT_NAME_DESCRIPTION); | ||
String rawNames = ConsoleReader.readLine(); | ||
|
||
return List.of(rawNames.split(NAME_DELIMITER)); | ||
} | ||
|
||
public static int inputLadderHeight() { | ||
System.out.println(INPUT_HEIGHT_DESCRIPTION); | ||
return Integer.parseInt(ConsoleReader.readLine()); | ||
} | ||
} |
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.
프로젝트 전반에 걸쳐 상수 네이밍 컨벤션을 전체 대문자로 해보는건 어떨까요?
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.
오... 한번도 상수화된 인스턴스를 상수라고 생각해 본 적이 없었는데 생각해보니 상수가 아닐 이유가 없네요.
상수 변수에 대해서는 전체 대문자 네이밍 컨벤션을 지키는 편인데, 이와 같은 경우는 사람마다 의견이 갈리는 것 같아요.
https://stackoverflow.com/questions/7259687/java-naming-convention-for-static-final-variables/7259738
로거를
LOGGER
로 쓸지logger
로 쓸지 결정하는 것이 비슷한 상황일 것 같은데, 저는 소문자가 편한 것 같아요!피케이는
static final
로 선언된 인스턴스 변수들도 모두 uppercase로 작성하시나요?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.
크루들과 이야기해보다가 이런 글을 발견했어요!
https://google.github.io/styleguide/javaguide.html#s5.2.4-constant-names