-
Notifications
You must be signed in to change notification settings - Fork 252
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단계 - 사다리 생성] 몰리(김지민) 미션 제출합니다. #314
Changes from all commits
09eff2d
c3611ac
fc9fbb3
6653ac1
6674645
ad1df2e
eeb57ad
8f7c474
233b959
62b7858
e4b4705
38c0034
8611540
b686b45
e3374fe
6248314
4dd6267
f3c1f14
77dcea7
75a2038
7332308
ef5a04a
8c89ecc
007b784
64f266e
19da1be
fe1bc1f
a023ce8
8bf415d
c683dd0
958d538
a80f45a
64a2af2
7460b56
b9ca5e0
9f73c3e
3897b47
d815880
1b21725
164e846
a178d6a
9ceeb00
6546037
ddcab56
76f9182
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,19 @@ | ||
### 기능 요구사항 정리 | ||
- [x] 사람의 이름을 갖는다. | ||
- [x] 이름은 최소 1글자 최대 5글자다. | ||
- [x] 라인은 사다리의 가로 한줄을 의미한다. | ||
- [x] 한 라인은 사람수보다 하나 적은 폭을 갖는다. | ||
- [x] 사다리의 경로(path:가로라인)은 연달아 있을 수 없다. (나란히 있을 수 없다.) | ||
- [x] 참여할 사람의 이름은 쉼표로 구분해 받는다. | ||
- [x] 사람은 최소 2명 참가해야한다. | ||
- [x] 사다리의 높이를 입력받는다. | ||
- [x] 최소 높이는 1 이상이다. | ||
- [x] 실행 결과를 출력한다. | ||
- [x] 사다리를 출력한다. | ||
- [x] 라인의 첫 부분은 ` ` 4칸으로 출력한다. | ||
- [x] 5칸과 가장 오른쪽에 `|`를 높이만큼 출력한다. | ||
- [x] 라인에 경로가 있는 경우 `-`를 5칸을 출력한다. | ||
- [x] 라인에 경로가 없는 경우 ` `를 5칸을 출력한다. | ||
- [x] 이름을 출력한다. | ||
- [x] 이름이 4글자 이하인 경우, 오른쪽에 하나의 공백을 두고 우측 정렬한다. | ||
- [x] 이름이 5글자인 경우, 공백없이 우측 정렬한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import controller.LadderGame; | ||
import view.InputView; | ||
import view.OutputView; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
try { | ||
final LadderGame ladderGame = new LadderGame(new InputView(), new OutputView()); | ||
ladderGame.play(); | ||
} catch (Exception e) { | ||
System.out.println(e.getMessage()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package controller; | ||
|
||
import dto.Result; | ||
import java.util.List; | ||
import model.Ladder; | ||
import model.People; | ||
import model.path.RandomPathGenerator; | ||
import view.InputView; | ||
import view.OutputView; | ||
|
||
public class LadderGame { | ||
private final InputView inputView; | ||
private final OutputView outputView; | ||
|
||
public LadderGame(final InputView inputView, final OutputView outputView) { | ||
this.inputView = inputView; | ||
this.outputView = outputView; | ||
} | ||
|
||
public void play() { | ||
final People people = initPeople(); | ||
final Ladder ladder = initLadder(people.getPersonCount()); | ||
|
||
final Result result = Result.from(people, ladder); | ||
outputView.printResult(result); | ||
} | ||
|
||
|
||
private People initPeople() { | ||
final List<String> names = inputView.inputNames(); | ||
return People.from(names); | ||
} | ||
|
||
private Ladder initLadder(final int personCount) { | ||
final int height = inputView.inputHeight(); | ||
return Ladder.from(height, personCount, new RandomPathGenerator()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dto; | ||
|
||
import java.util.List; | ||
import model.Line; | ||
|
||
public record LineInfo(List<Boolean> lineInfo) { | ||
public static LineInfo from(final Line line) { | ||
final List<Boolean> lineInfo = line.getExistFlags(); | ||
return new LineInfo(lineInfo); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package dto; | ||
|
||
import java.util.List; | ||
import model.Ladder; | ||
import model.People; | ||
|
||
public record Result(List<String> names, List<LineInfo> lines) { | ||
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 활용 👍 |
||
public static Result from(final People people, final Ladder ladder) { | ||
final List<String> names = people.getNames(); | ||
final List<LineInfo> lines = ladder.getLines() | ||
.stream() | ||
.map(LineInfo::from) | ||
.toList(); | ||
|
||
return new Result(names, lines); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package model; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import model.path.PathGenerator; | ||
|
||
public class Ladder { | ||
private final List<Line> lines; | ||
|
||
private Ladder(final List<Line> lines) { | ||
this.lines = lines; | ||
} | ||
|
||
public static Ladder from(final int height, final int personCount, final PathGenerator pathGenerator) { | ||
validateHeight(height); | ||
final List<Line> lines = new ArrayList<>(); | ||
for (int i = 0; i < height; i++) { | ||
int pathCount = personCount - 1; | ||
final Line line = new Line(pathGenerator.generate(pathCount)); | ||
lines.add(line); | ||
} | ||
return new Ladder(lines); | ||
} | ||
|
||
private static void validateHeight(final int height) { | ||
if (height < 1) { | ||
throw new IllegalArgumentException("사다리의 높이는 1 이상이어야 합니다."); | ||
} | ||
} | ||
|
||
public List<Line> getLines() { | ||
return lines; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package model; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
import model.path.Path; | ||
|
||
public class Line { | ||
private static final int MIN_PATH_SIZE = 1; | ||
|
||
private final List<Path> paths; | ||
|
||
public Line(final List<Path> paths) { | ||
validatePathCount(paths.size()); | ||
checkContinuousPaths(paths); | ||
this.paths = paths; | ||
} | ||
|
||
private void validatePathCount(final int pathSize) { | ||
if (pathSize < MIN_PATH_SIZE) { | ||
throw new IllegalArgumentException("사다리의 경로는 비어있더라도 최소 1개 이상이여야 합니다."); | ||
} | ||
} | ||
|
||
private void checkContinuousPaths(final List<Path> paths) { | ||
for (int i = 0; i < paths.size() - 1; i++) { | ||
final Path left = paths.get(i); | ||
final Path right = paths.get(i + 1); | ||
validateContinuousPaths(left, right); | ||
} | ||
} | ||
|
||
private void validateContinuousPaths(final Path left, final Path right) { | ||
if (left == Path.EXIST && right == Path.EXIST) { | ||
throw new IllegalArgumentException("사다리의 경로는 연달아 있을 수 없습니다."); | ||
} | ||
} | ||
|
||
public List<Boolean> getExistFlags() { | ||
return paths.stream() | ||
.map(Path::isExist) | ||
.toList(); | ||
} | ||
|
||
@Override | ||
public boolean equals(final Object o) { | ||
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. Line에서 equals 를 재정의 해주신 이유가 궁금해요! 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. Ladder 테스트에서 생성한 Ladder의 Line이 주입한 Line과 동일한지를 통해 Ladder가 잘 생성되는지를 검증하려고 추가를 했습니다...ㅎㅎ |
||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
Line line = (Line) o; | ||
return Objects.equals(paths, line.paths); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package model; | ||
|
||
import java.util.List; | ||
|
||
public class People { | ||
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 final int MIN_PERSON_COUNT = 2; | ||
|
||
private final List<Person> personGroup; | ||
|
||
private People(final List<Person> personGroup) { | ||
validatePersonCount(personGroup.size()); | ||
this.personGroup = personGroup; | ||
} | ||
|
||
private void validatePersonCount(final int personCount) { | ||
if (personCount < MIN_PERSON_COUNT) { | ||
throw new IllegalArgumentException("사람은 최소 2명 참가해야 합니다."); | ||
} | ||
} | ||
|
||
public static People from(final List<String> names) { | ||
final List<Person> personGroup = names.stream() | ||
.map(Person::new) | ||
.toList(); | ||
return new People(personGroup); | ||
} | ||
|
||
public List<String> getNames() { | ||
return personGroup.stream() | ||
.map(Person::getName) | ||
.toList(); | ||
} | ||
|
||
public int getPersonCount() { | ||
return personGroup.size(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package model; | ||
|
||
public class Person { | ||
private static final int MIN_NAME_LENGTH = 1; | ||
private static final int MAX_NAME_LENGTH = 5; | ||
|
||
private final String name; | ||
|
||
public Person(final String name) { | ||
validateNameLength(name); | ||
this.name = name; | ||
} | ||
|
||
private void validateNameLength(final String name) { | ||
final int length = name.length(); | ||
if (length < MIN_NAME_LENGTH || length > MAX_NAME_LENGTH) { | ||
throw new IllegalArgumentException("이름은 최소 1글자 최대 5글자여야 합니다."); | ||
} | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package model.path; | ||
|
||
import java.util.List; | ||
|
||
public class FixedPathGenerator implements PathGenerator { | ||
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 final List<Path> lines; | ||
|
||
public FixedPathGenerator(final List<Path> lines) { | ||
this.lines = lines; | ||
} | ||
|
||
@Override | ||
public List<Path> generate(final int count) { | ||
return this.lines; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package model.path; | ||
|
||
public enum Path { | ||
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. 사다리 경로를 enum으로 표현 👍 👍 👍 👍 |
||
EXIST(true), | ||
NOT_EXIST(false); | ||
|
||
private final boolean isExist; | ||
|
||
Path(final boolean isExist) { | ||
this.isExist = isExist; | ||
} | ||
|
||
public boolean isExist() { | ||
return isExist; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package model.path; | ||
|
||
import java.util.List; | ||
|
||
public interface PathGenerator { | ||
List<Path> generate(final int count); | ||
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. 경로 생성은 interface로 추상화 👍 어떤 의도로 추상화를 해주셨는지 궁금해요! 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. [추상화 시도 이유]처음 테스트 코드를 작성할 때는, // 수정 전의 LineTest.java
Line line = new Line(personCount);
for (int i = 0; i < personCount - 1; i++) {
assertThat(line.get(i)).isNotEqualTo(line.get(i + 1));
} 하지만 위 테스트는 제어할 수 없는 Random 때문에 신뢰할 수 없는 테스트라는 생각이 들었습니다...! 때문에 Line에는 List를 주입받고 Ladder에서 전달을 해야겠네? -> 그러면 Ladder에서 Random을 사용하는 건 괜찮을까? -> 인터페이스를 받아서 상황에 따른 구현체를 사용하자 라는 생각의 흐름으로 나아갔던 것 같아요! 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. [추가 궁금한 점 - RandomPathGenerator의 생성시점]그런데 위 생각을 작성하다가 궁금한 점이 생겼어요. 찰리! // LadderGame.java
private Ladder initLadder(final int personCount) {
final int height = inputView.inputHeight();
return Ladder.from(height, personCount, new RandomPathGenerator()); // RandomPathGenerator 생성
} RandomPathGenerator의 생성 시점은 언제가 더 적절할 지에 대한 찰리의 의견이 궁금합니다! 저는 일단 2가지의 시점을 생각해보았어요!
1번 방법은, 제가 현재 2번 방법을 선택한 이유는 찰리의 의견이 궁금합니다..! 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. 중요한 고민이라고 생각해요!! 👍 각각의 방법에서 생각하는 장점도 말씀해주셔서 감사해요 🙇
동의하는 부분입니다 👍 저도 LadderGame을 테스트했을 때 얻는 장점이 커보이지 않아요
좋은 전략이라 생각해요 👍 |
||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,34 @@ | ||||||
package model.path; | ||||||
|
||||||
import java.util.ArrayList; | ||||||
import java.util.List; | ||||||
import java.util.Random; | ||||||
|
||||||
public class RandomPathGenerator implements PathGenerator { | ||||||
private final Random random = new Random(); | ||||||
|
||||||
@Override | ||||||
public List<Path> generate(final int count) { | ||||||
final List<Path> paths = new ArrayList<>(); | ||||||
while (paths.size() < count) { | ||||||
Path randomPath = getNextPath(paths); | ||||||
paths.add(randomPath); | ||||||
} | ||||||
return paths; | ||||||
} | ||||||
|
||||||
private Path getNextPath(final List<Path> paths) { | ||||||
if (!paths.isEmpty() && getLastPath(paths) == Path.EXIST) { | ||||||
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. 아래와 같이 메서드를 추출하면 한눈에 알 수 있을것같아요
Suggested change
|
||||||
return Path.NOT_EXIST; | ||||||
} | ||||||
if (random.nextBoolean()) { | ||||||
return Path.EXIST; | ||||||
} | ||||||
return Path.NOT_EXIST; | ||||||
} | ||||||
|
||||||
private Path getLastPath(final List<Path> paths) { | ||||||
return paths.get(paths.size() - 1); | ||||||
} | ||||||
|
||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package view; | ||
|
||
import java.util.List; | ||
import java.util.Scanner; | ||
import view.parser.NameParser; | ||
|
||
public class InputView { | ||
private final Scanner scanner = new Scanner(System.in); | ||
|
||
public List<String> inputNames() { | ||
System.out.println("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)"); | ||
final String text = scanner.next(); | ||
return NameParser.parse(text); | ||
} | ||
|
||
public int inputHeight() { | ||
System.out.println(); | ||
System.out.println("최대 사다리 높이는 몇 개인가요?"); | ||
return scanner.nextInt(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package view; | ||
|
||
import dto.LineInfo; | ||
import dto.Result; | ||
import view.formatter.LineFormatter; | ||
import view.formatter.NamesFormatter; | ||
import java.util.List; | ||
|
||
public class OutputView { | ||
|
||
public void printResult(final Result result) { | ||
System.out.println(); | ||
System.out.println("실행결과"); | ||
System.out.println(); | ||
final List<String> names = result.names(); | ||
printNames(names); | ||
printLines(result.lines()); | ||
} | ||
|
||
public void printNames(final List<String> names) { | ||
System.out.println(NamesFormatter.format(names)); | ||
} | ||
|
||
public void printLines(final List<LineInfo> lines) { | ||
lines.forEach(this::printLine); | ||
} | ||
|
||
private void printLine(final LineInfo line) { | ||
final List<Boolean> paths = line.lineInfo(); | ||
System.out.println(LineFormatter.format(paths)); | ||
} | ||
} |
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.
깔끔한 요구사항 정리 👍