Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
125 changes: 125 additions & 0 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.Scanner;

import domain.Columns;
import domain.Ladder;
import domain.LadderHeight;
import domain.Participants;
import domain.RungLength;
import service.LadderNavigator;
import service.RandomConnectionGenerator;
import view.LadderRenderer;

public class App {
public static void main(String[] args) {
Copy link

Choose a reason for hiding this comment

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

Image

main에 너무 많이 몰려 있어요. 함수나 메서드로 쪼개는게 필요해보여요

Copy link
Author

Choose a reason for hiding this comment

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

아차차... 알려주셔서 감사합니다🙇
바로 수정하도록 하겠습니다!!

try (Scanner scanner = new Scanner(System.in)) {
Participants participants = readParticipants(scanner);
List<String> labels = readLabels(scanner, participants.size());
int heightCount = readHeight(scanner);
Ladder ladder = buildLadder(participants.size(), heightCount);
renderLadder(participants.names(), labels, ladder);
queryAndPrintResults(scanner, participants, labels, ladder);
}
}

private static Participants readParticipants(Scanner scanner) {
while (true) {
List<String> names = readCommaSeparated(scanner, "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)");
try { return Participants.of(names); }
catch (IllegalArgumentException e) { System.out.println(e.getMessage()); }
}
}

private static List<String> readLabels(Scanner scanner, int count) {
while (true) {
List<String> labels = readCommaSeparated(scanner, "실행 결과를 입력하세요. (결과는 쉼표(,)로 구분하세요)");
if (labels.size() != count) { System.out.println("결과의 개수는 참여자 수(" + count + ")와 같아야 합니다"); continue; }
return labels;
}
}

private static List<String> readCommaSeparated(Scanner scanner, String prompt) {
System.out.println(prompt);
return Arrays.stream(scanner.nextLine().trim().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.toList();
}

private static int readHeight(Scanner scanner) {
System.out.println();
System.out.println("최대 사다리 높이는 몇 개인가요?");
return Integer.parseInt(scanner.nextLine().trim());
}

private static Ladder buildLadder(int numNames, int heightCount) {
Columns columns = Columns.of(numNames);
LadderHeight height = LadderHeight.of(heightCount);
RandomConnectionGenerator generator = new RandomConnectionGenerator(new Random());
return generator.generateLadder(height, columns);
}

private static void renderLadder(List<String> names, List<String> labels, Ladder ladder) {
Columns columns = Columns.of(names.size());
RungLength rungLength = RungLength.defaultFive();
LadderRenderer renderer = new LadderRenderer();
System.out.println("\n사다리 결과\n");
System.out.println(renderer.renderNamesHeader(names, rungLength));
renderer.print(ladder, columns, rungLength);
System.out.println(renderer.renderBottomLabels(labels, rungLength));
}

private static void queryAndPrintResults(Scanner scanner, Participants participants, List<String> labels, Ladder ladder) {
Columns columns = Columns.of(participants.size());
LadderNavigator navigator = new LadderNavigator();
while (true) {
String who = promptWho(scanner);
if (handleSelection(navigator, ladder, columns, participants, labels, who)) break;
}
}

private static String promptWho(Scanner scanner) {
System.out.println();
System.out.println("결과를 보고 싶은 사람은?");
String who = scanner.nextLine().trim();
System.out.println("\n실행 결과");
return who;
}

private static boolean handleSelection(
LadderNavigator navigator,
Ladder ladder,
Columns columns,
Participants participants,
List<String> labels,
String who
) {
if (who.equalsIgnoreCase("all")) { handleAllRequest(navigator, ladder, columns, participants, labels); return true; }
int index = participants.indexOf(who);
if (index < 0) { System.out.println("존재하지 않는 이름입니다"); return false; }
printSingleResult(navigator, ladder, columns, index, labels);
return false;
}

private static void handleAllRequest(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List<String> labels) {
printAllResults(navigator, ladder, columns, participants, labels);
}

private static void printAllResults(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List<String> labels) {
for (int i = 0; i < columns.count(); i++) { printSingleMapping(navigator, ladder, columns, participants, labels, i); }
}

private static void printSingleResult(LadderNavigator navigator, Ladder ladder, Columns columns, int index, List<String> labels) {
int dest = navigator.traverse(ladder, columns, index);
System.out.println(labels.get(dest));
}

private static void printSingleMapping(LadderNavigator navigator, Ladder ladder, Columns columns, Participants participants, List<String> labels, int i) {
int dest = navigator.traverse(ladder, columns, i);
String name = participants.get(i);
String label = labels.get(dest);
System.out.println(name + " : " + label);
}
}
30 changes: 30 additions & 0 deletions src/main/java/domain/Columns.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package domain;

public final class Columns {
Copy link

Choose a reason for hiding this comment

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

final class로 만든 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

열의 개수는 최소 2개라는 불변 조건을 지켜야 하므로, 이 조건을 항상 만족할 수 있도록 상속을 막는 final을 사용했습니다!

Copy link

Choose a reason for hiding this comment

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

Rows 이름만 보면 내부적으로 Row 리스트를 가지고 있을 것 같은데 상수 하나만 존재하는게 살짝 어색해요.
Rows 말고 다른 이름은 어떨까요?
아니면 Rows 내부적으로 Row를 가지고 길이나 갯수가 필요할 경우 리스트의 size를 반환하는건 어떨까요?

동일

private final int count;

private Columns(int count) {
this.count = count;
}

public static Columns of(int count) {
if (count < 2) {
throw new IllegalArgumentException("열의 개수는 2 이상이어야 합니다");
}
return new Columns(count);
}

public static Columns fixedFour() {
return of(4);
}
Copy link

Choose a reason for hiding this comment

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

사다리 크기를 입력 받아 생성하기 전에 쓰던 코드가 남아있는 것 같아요.
지금처럼 상수 4로 Columns를 생성하는 경우, 이 상수는 누구의 책임일까요? 상수를 통한 생성이 Columns에 있는게 맞을까요?

상수를 이용해 Columns를 구성할 경우, 해당 상수는 Columns를 생성하는 주체 측에 위치하는 것이 바람직하다고 생각해요. 테스트 코드를 작성하면서 fixture를 정의할 때 Columns 클래스 내부에 fixture를 작성하지 않는 것처럼요.

Copy link
Author

Choose a reason for hiding this comment

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

말씀해 주신 것처럼 Columns 에서 검증만 맡고 4같은 상수는 생성하는 주체에 있는 것이 올바른 것 같습니다.
상수 4로 columns를 생성하면 이 상수는 상수 4를 생성하라고 호출한 생성 주체가 책임이라고 생각합니다.


public int count() {
return count;
}

public int numberOfSegments() {
return count - 1;
}
}


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

public enum Connection {
CONNECTED,
EMPTY
}


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

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

public final class Ladder {
private final List<Row> rows;

private Ladder(List<Row> rows) {
this.rows = Collections.unmodifiableList(new ArrayList<>(rows));
}

public static Ladder of(List<Row> rows) {
return new Ladder(rows);
}

public List<Row> rows() {
return rows;
}
}


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

public final class LadderHeight {
private final int count;

private LadderHeight(int count) {
this.count = count;
}

public static LadderHeight of(int count) {
if (count < 1) {
throw new IllegalArgumentException("행의 개수는 1 이상이어야 합니다");
}
return new LadderHeight(count);
}

public int count() {
return count;
}
}


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

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

public final class Participants {
private final List<String> names;

private Participants(List<String> names) {
this.names = Collections.unmodifiableList(new ArrayList<>(names));
}

public static Participants of(List<String> rawNames) {
List<String> names = normalizeNames(rawNames);
requireNonEmpty(names);
requireMaxLength(names, 5);
return new Participants(names);
}

private static List<String> normalizeNames(List<String> rawNames) {
List<String> result = new ArrayList<>();
for (String n : rawNames) {
String v = n == null ? "" : n.trim();
if (!v.isEmpty()) { result.add(v); }
}
return result;
}

private static void requireNonEmpty(List<String> names) {
if (names.isEmpty()) { throw new IllegalArgumentException("이름을 한 개 이상 입력해 주세요"); }
}

private static void requireMaxLength(List<String> names, int max) {
boolean tooLongExists = names.stream().anyMatch(n -> n.length() > max);
if (tooLongExists) {
throw new IllegalArgumentException("각 이름은 최대 " + max + "글자까지 가능합니다");
}
}

public int size() {
return names.size();
}

public int indexOf(String name) {
return names.indexOf(name);
}

public String get(int index) {
return names.get(index);
}

public List<String> names() {
return names;
}
}


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

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

public final class Row {
private final List<Connection> connections;

private Row(List<Connection> connections) {
this.connections = Collections.unmodifiableList(new ArrayList<>(connections));
Copy link

Choose a reason for hiding this comment

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

이런게 있었군요! 자세한 내용이 궁금해서 찾아봤어요!
개인적으로 배열을 복사해서 unmodifiable하게 만드는 대신에 배열을 복사해서 캡슐화를 잘하면 되지 않을까라는 생각이 드는 것 같아요. 태진님의 의견이 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

캡슐화를 진행해도 좋을 것 같습니다!! 😀

코드 작성 당시, 저 방식이 성능과 안전성을 모두 고려한 좋은 방법이라고 생각했었습니다. 생성 시 한 번만 방어적 복사를 해두면 잦은 호출에도 성능 부담이 적고, 불변인 enum 요소를 변경 불가 뷰로 제공하니 데이터 안정성도 충분히 확보된다고 보았습니다.

}

public static Row of(List<Connection> connections) {
validate(connections);
return new Row(connections);
}

private static void validate(List<Connection> connections) {
Connection previous = Connection.EMPTY;
for (Connection current : connections) {
if (previous == Connection.CONNECTED && current == Connection.CONNECTED) {
throw new IllegalArgumentException("인접한 연결은 허용되지 않습니다");
}
previous = current;
}
}

public List<Connection> connections() {
return connections;
}
}


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

public final class RungLength {
private final int length;

private RungLength(int length) {
this.length = length;
}

public static RungLength of(int length) {
if (length < 1) {
throw new IllegalArgumentException("길이는 1 이상이어야 합니다");
}
return new RungLength(length);
}

public static RungLength defaultFive() {
return of(5);
}
Comment on lines +17 to +19
Copy link

Choose a reason for hiding this comment

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

사다리 크기를 입력 받아 생성하기 전에 쓰던 코드가 남아있는 것 같아요.
지금처럼 상수 4로 Columns를 생성하는 경우, 이 상수는 누구의 책임일까요? 상수를 통한 생성이 Columns에 있는게 맞을까요?

상수를 이용해 Columns를 구성할 경우, 해당 상수는 Columns를 생성하는 주체 측에 위치하는 것이 바람직하다고 생각해요. 테스트 코드를 작성하면서 fixture를 정의할 때 Columns 클래스 내부에 fixture를 작성하지 않는 것처럼요.

동일


public int length() {
return length;
}

public String dashes() {
return "-".repeat(length);
}

public String spaces() {
return " ".repeat(length);
}
}


37 changes: 37 additions & 0 deletions src/main/java/service/LadderNavigator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package service;

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

import domain.Columns;
import domain.Connection;
import domain.Ladder;
import domain.Row;

public final class LadderNavigator {
public int traverse(Ladder ladder, Columns columns, int startColumn) {
int currentColumn = startColumn;
for (Row row : ladder.rows()) {
if (currentColumn < columns.numberOfSegments()
&& row.connections().get(currentColumn) == Connection.CONNECTED) {
currentColumn += 1;
continue;
}
if (currentColumn > 0
&& row.connections().get(currentColumn - 1) == Connection.CONNECTED) {
currentColumn -= 1;
}
}
return currentColumn;
}

public List<Integer> traverseAll(Ladder ladder, Columns columns) {
List<Integer> results = new ArrayList<>();
for (int start = 0; start < columns.count(); start++) {
results.add(traverse(ladder, columns, start));
}
return results;
}
}


Loading