Skip to content
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단계 - 사다리 생성] 에버(손채영) 미션 제출합니다. #316

Merged
merged 63 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
88b71e0
docs: 구현할 기능 요구사항 작성
helenason Feb 20, 2024
63efb03
docs: 참여자 이름 관련 요구사항 수정
helenason Feb 20, 2024
d6d3437
test: 참여자 이름 테스트 코드 작성
helenason Feb 20, 2024
b9ae327
test: 비어있는 이름에 대한 에러메시지 수정 및 문법 관련 에러 해결
helenason Feb 20, 2024
50bb66e
feat: 참여자 이름 도메인 생성
helenason Feb 20, 2024
dba6496
test: 참여자들 테스트 코드 작성
helenason Feb 20, 2024
2460723
test: 참여자 테스트 코드 작성
helenason Feb 20, 2024
df6505e
feat: 멤버 도메인 생성
helenason Feb 20, 2024
dbb5bf6
feat: 참여자 이름 가져오는 메서드 추가
helenason Feb 20, 2024
d862bce
test: 참여자들 테스트 null 입력하는 경우 추가 및 에러 메시지 검증 제외
helenason Feb 20, 2024
960163e
feat: 참여자들 도메인 생성
helenason Feb 20, 2024
7f82f81
test: 가로줄 테스트 코드 작성
helenason Feb 22, 2024
afd6d85
feat: 가로줄 도메인 및 가로줄 생성을 위한 전략 인터페이스 생성
helenason Feb 22, 2024
6f309ff
test: 가로줄 테스트 도메인에 맞게 수정
helenason Feb 22, 2024
b902809
test: 사다리 높이 도메인 테스트 코드 작성
helenason Feb 22, 2024
63aff0e
feat: 사다리 높이 도메인 생성
helenason Feb 22, 2024
6ed33af
test: 사다리 전체 도메인 테스트 코드 작성
helenason Feb 22, 2024
7a2b7f4
feat: 사다리 전체 도메인 생성
helenason Feb 22, 2024
b9fc1f8
feat: 사다리 높이 도메인 getter 추가
helenason Feb 22, 2024
8fd9449
test: 사다리 전체 도메인에 맞게 수정
helenason Feb 22, 2024
e8d008c
feat: view 클래스 생성
helenason Feb 22, 2024
416a256
feat: 출력 로직 구현
helenason Feb 22, 2024
1215174
feat: 참여자들 도메인에 데이터 반환 메서드 추가
helenason Feb 22, 2024
e198c19
feat: 게임 실행 도메인 생성
helenason Feb 22, 2024
bd00b5d
feat: 컨트롤러 생성
helenason Feb 22, 2024
4cd2c8c
feat: main 메서드 생성
helenason Feb 22, 2024
fec15a1
style: 공백 정리
helenason Feb 22, 2024
2917f27
feat: 에러 발생 시 재입력받는 로직 추가
helenason Feb 22, 2024
8bd7344
refactor: 출력 문자열 개행 제거
helenason Feb 22, 2024
a8b502e
feat: 사다리 연결 여부 Enum 클래스 구현
helenason Feb 22, 2024
fbe8f4e
refactor: 사다리 연결 여부 Enum으로 관리
helenason Feb 22, 2024
2a3966e
test: 사다리 연결 여부 Enum 변경에 맞게 테스트 수정
helenason Feb 22, 2024
e5de9eb
refactor: 에러 처리 prefix 상수 사용
helenason Feb 22, 2024
7b92aa4
refactor: 에러 처리 prefix 상수 사용
helenason Feb 22, 2024
8546fb2
style: 코드 내 불필요한 개행 제거
helenason Feb 22, 2024
b61e163
test: 사다리 연결 여부 Enum화에 따른 테스트 수정
helenason Feb 22, 2024
39397ee
docs: 구현된 기능 목록 완료 표시
helenason Feb 22, 2024
fc00918
refactor: 이름의 최대 글자 상수 처리
helenason Feb 22, 2024
829f6be
style: TODO 목록 제거
helenason Feb 22, 2024
90f7b30
test: 생성자 테스트 추가, 테스트명 및 메서드명 컨벤션 통일
helenason Feb 22, 2024
8052c65
refactor: PointStrategy의 참조 방식 변경
helenason Feb 22, 2024
0c478c6
refactor: 불필요한 import 제거
helenason Feb 22, 2024
9dd66ca
refactor: String 덧셈 연산 StringBuilder 사용하여 불필요한 객체 생성 과정 제거
helenason Feb 24, 2024
7c79b51
refactor: 사용하고 있지 않은 isConnected 변수 제거
helenason Feb 24, 2024
bb9e0cc
refactor: 사다리의 연결 상태를 나타내는 클래스 직관적인 이름으로 변경
helenason Feb 24, 2024
f22fe8a
refactor: 다음 포인트의 연결 여부를 생성하는 메서드 연결 클래스로 역할 분리
helenason Feb 24, 2024
057fd7a
refactor: 문자열 가공 역할 StringParser 클래스로 분리
helenason Feb 24, 2024
44fa207
refactor: 컨트롤러의 파라미터를 통해 에러 핸들러 주입
helenason Feb 24, 2024
1a91b97
refactor: 이름 입력 역할과 객체 생성 역할 다른 메서드로 분리
helenason Feb 24, 2024
d4efe94
refactor: Height 객체가 해당 객체를 사용하는 객체 내부에서 생성되도록 수정 및 파싱 로직 책임을 외부로 이전
helenason Feb 24, 2024
c4e12b4
style: 불필요한 공백 제거
helenason Feb 24, 2024
24a26bd
refactor: 사다리 연결 여부와 출력 형태 매칭 로직을 리졸버로 이동
helenason Feb 24, 2024
011baed
refactor: MessageResolver 클래스 OutputView로 통합
helenason Feb 25, 2024
84619c4
style: 불필요한 주석 제거
helenason Feb 25, 2024
cb3e036
refactor: 출력되는 문자열 상수로 처리
helenason Feb 25, 2024
fdff455
refactor: 테스트에서의 재사용을 위해 상수의 접근제어자 변경
helenason Feb 25, 2024
5183c25
test: 도메인 생성 실패의 경우 에러메시지 검증 로직 추가
helenason Feb 25, 2024
74b7287
refactor: point와 connection 혼용하던 방식 connection으로 통일
helenason Feb 25, 2024
25dbd20
refactor: 메서드의 길이를 줄이기 위해 추가 메서드로 분리
helenason Feb 25, 2024
7abb36c
test: 절대 연속으로 사다리가 연결될 수 없음을 확인하는 테스트 추가
helenason Feb 25, 2024
a076bae
refactor: Lines 클래스 정적 팩토리 메서드 도입
helenason Feb 25, 2024
c4ae098
refactor: Members 클래스 정적 팩토리 메서드 도입
helenason Feb 25, 2024
332b757
refactor: Line 클래스 정적 팩토리 메서드 도입
helenason Feb 25, 2024
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
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
# java-ladder
## 기능 요구사항
### 참여자
- [x] 이름은 1글자부터 5글자까지 부여할 수 있다.
- [x] 이름은 쉼표(,)를 기준으로 구분한다.
- [x] 이름은 중복될 수 없다.
- [x] 이름은 알파벳 대소문자 + 숫자로 구성되어야 한다.
- [x] 참여자 수는 최소 2명부터 최대 15명까지이다.

사다리 타기 미션 저장소
### 사다리
- [x] 사다리 가로줄의 생성 여부는 랜덤으로 결정된다.
- [x] 사다리 가로줄 라인이 겹치지 않도록 해야 한다.
- [x] `|-----|-----|` 와 같은 모양은 허용하지 않는다.
- [x] 사다리 높이는 1부터 20까지 가능하다.

## 우아한테크코스 코드리뷰
### 입력
- [x] 참여할 사람의 이름을 쉼표(,)로 구분하여 입력받는다.
- [x] 최대 사다리 높이를 입력받는다.
- [x] 잘못된 형태로 입력된 경우 다시 입력받는다.

- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
### 출력
- [x] 사람 이름이 출력되고, 그 다음줄부터 사다리가 출력된다.
- [x] 사람 이름이 5자 미만인 경우 이름의 마지막 글자가 사다리 세로줄의 바로 왼쪽 열에 위치하게 한다.
- [x] 사람 이름이 5자인 경우 이름의 마지막 글자가 사다리 세로줄과 같은 열에 위치하게 한다.
- [x] 사다리 각 행의 가장 왼쪽에는 4칸의 공백이 있다.
- [x] 사다리 세로줄 간의 간격은 5칸으로 한다.
16 changes: 16 additions & 0 deletions src/main/java/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import controller.GameController;
import error.ErrorHandler;
import view.InputView;
import view.OutputView;

public class Main {

public static void main(String[] args) {
InputView inputView = new InputView();
OutputView outputView = new OutputView();
ErrorHandler errorHandler = new ErrorHandler();

GameController gameController = new GameController(inputView, outputView, errorHandler);
gameController.run();
}
}
48 changes: 48 additions & 0 deletions src/main/java/controller/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package controller;

import domain.Game;
import domain.Lines;
import domain.Members;
import domain.StringParser;
import error.ErrorHandler;
import strategy.RandomConnectionStrategy;
import view.InputView;
import view.OutputView;

public class GameController {

private final InputView inputView;
private final OutputView outputView;
private final ErrorHandler errorHandler;

public GameController(InputView inputView, OutputView outputView, ErrorHandler errorHandler) {
this.inputView = inputView;
this.outputView = outputView;
this.errorHandler = errorHandler;
}

public void run() {

Members members = makeMembers();

Lines lines = makeLines(members);

Game game = new Game(members, lines);
outputView.printResult(game);
}

private Members makeMembers() {
return errorHandler.readUntilNoError(() -> {
String rawNames = inputView.readNames();
return Members.from(rawNames);
});
}
Comment on lines +34 to +39

Choose a reason for hiding this comment

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

입력 + Members 생성까지 해서 너무 많은 역할을 한다고 피드백 드렸었는데, 이번엔 메소드 역할에 에러 핸들링까지 추가되었어요!
어떻게 변경해보면 좋을까요?


private Lines makeLines(Members members) {
return errorHandler.readUntilNoError(() -> {
String rawHeight = inputView.readHeight();
int height = StringParser.stringToInt(rawHeight);
return Lines.of(members.getCount(), height, new RandomConnectionStrategy());
});
}
}
19 changes: 19 additions & 0 deletions src/main/java/domain/Connection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package domain;

import strategy.ConnectionStrategy;

public enum Connection {

CONNECTED,
DISCONNECTED;

Connection() {
}

public Connection makeNextConnection(ConnectionStrategy connectionStrategy) {
if (this == CONNECTED) {
return DISCONNECTED;
}
return connectionStrategy.generateConnection();
}
}
20 changes: 20 additions & 0 deletions src/main/java/domain/Game.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package domain;

public class Game {

private final Members members;
private final Lines lines;

public Game(Members members, Lines lines) {
this.members = members;
this.lines = lines;
}

public Members getMembers() {
return members;
}

public Lines getLines() {
return lines;
}
}
24 changes: 24 additions & 0 deletions src/main/java/domain/Height.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain;

public class Height {

Comment on lines +3 to +4

Choose a reason for hiding this comment

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

테스트에서만 사용하는 클래스가 되었네요! 이제 안쓰는 클래스일까요?

Copy link
Member Author

@helenason helenason Feb 29, 2024

Choose a reason for hiding this comment

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

리팩토링하는 과정에서 Height 클래스 내의 로직들을 외부로 넘기다보니 무의미한 클래스가 되어버렸네요...
해당 클래스는 삭제했습니다!

public static final int MIN_HEIGHT = 1;
public static final int MAX_HEIGHT = 20;

private final int value;

public Height(int value) {
validateRange(value);
this.value = value;
}

private void validateRange(int value) {
if (value < MIN_HEIGHT || value > MAX_HEIGHT) {
throw new IllegalArgumentException(MIN_HEIGHT + " 이상 " + MAX_HEIGHT + " 이하의 숫자를 입력해 주세요.");
}
}

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

import java.util.ArrayList;
import java.util.List;
import strategy.ConnectionStrategy;

public class Line {

private final List<Connection> connections;

private Line(List<Connection> connections) {
this.connections = connections;
}

public static Line from(int memberCount, ConnectionStrategy connectionStrategy) {
List<Connection> connections = new ArrayList<>();
connections.add(connectionStrategy.generateConnection());

for (int i = 1; i < memberCount - 1; i++) {
Connection previous = connections.get(i - 1);
Connection next = previous.makeNextConnection(connectionStrategy);
connections.add(next);
}
return new Line(connections);
}

public List<Connection> getConnections() {
return connections;
}
}
27 changes: 27 additions & 0 deletions src/main/java/domain/Lines.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package domain;

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

public class Lines {

private final List<Line> lines;

private Lines(List<Line> lines) {
this.lines = lines;
}

public static Lines of(int memberCount, int height, ConnectionStrategy connectionStrategy) {
List<Line> lines = new ArrayList<>();
for (int i = 0; i < height; i++) {
lines.add(Line.from(memberCount, connectionStrategy));
}
return new Lines(lines);
}

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

public class Member {

private final Name name;

public Member(String rawName) {
this.name = new Name(rawName);
}

public String getName() {
return name.getName();
}
}
56 changes: 56 additions & 0 deletions src/main/java/domain/Members.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package domain;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Members {

private static final String DELIMITER = ",";
private static final int MIN_MEMBER_COUNT = 2;
private static final int MAX_MEMBER_COUNT = 15;

private final List<Member> members;

private Members(List<Member> members) {
this.members = members;
}

public static Members from(String rawNames) {
List<String> names = StringParser.splitByDelimiter(rawNames, DELIMITER);

Choose a reason for hiding this comment

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

이 객체의 역할이 맞을까요? input이 ,으로 split할 수 있는 형태로 들어온다는건 어떤 객체의 책임이 적절할까요?
현재는 만약 UI가 console 이 아니라 Web이고 각 이름을 개행으로 구분해서 입력한다면 이 도메인까지 수정되어야 할 것 같은데, view와 비즈니스 로직이 분리되었다고 볼 수 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

헉 그러네요. 구분자가 변경될 경우 Members 클래스가 수정되어야하는 상황이 발생하네요.
그렇다면 도메인보다는 컨트롤러의 책임이라고 생각됩니다.
생각 반영하여 코드 수정하겠습니다!

validateDuplication(names);
validateCount(names);
List<Member> members = makeMembers(names);
return new Members(members);
}

private static void validateDuplication(List<String> names) {
Set<String> nonDuplicated = new HashSet<>(names);
if (names.size() != nonDuplicated.size()) {
throw new IllegalArgumentException("이름은 서로 중복될 수 없습니다.");
}
}

private static void validateCount(List<String> names) {
if (names.size() < MIN_MEMBER_COUNT || names.size() > MAX_MEMBER_COUNT) {
throw new IllegalArgumentException("참여자는 " + MIN_MEMBER_COUNT + "~" + MAX_MEMBER_COUNT + "명만 허용됩니다.");
}
}

private static List<Member> makeMembers(List<String> names) {
List<Member> members = new ArrayList<>();
names.forEach(name -> members.add(new Member(name)));
return members;
}
Comment on lines +41 to +45

Choose a reason for hiding this comment

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

Suggested change
private static List<Member> makeMembers(List<String> names) {
List<Member> members = new ArrayList<>();
names.forEach(name -> members.add(new Member(name)));
return members;
}
private static List<Member> makeMembers(List<String> names) {
return names.stream()
.map(Member::new)
.toList();
}


public int getCount() {
return members.size();
}

public List<String> getNames() {
return members.stream()
.map(Member::getName)
.toList();
}
}
43 changes: 43 additions & 0 deletions src/main/java/domain/Name.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package domain;

public class Name {

private static final int MIN_NAME_LENGTH = 1;
private static final int MAX_NAME_LENGTH = 5;
private static final String FORMAT_NAME = "^[A-Za-z0-9]+$";

private final String name;

public Name(String name) {
validate(name);
this.name = name;
}

private void validate(String name) {
validateNull(name);
validateLength(name);
validatePattern(name);
}

private void validateNull(String name) {
if (name == null) {
throw new IllegalArgumentException("이름에 null을 입력할 수 없습니다.");
}
}

private void validateLength(String name) {
if (name.length() < MIN_NAME_LENGTH || name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException(MIN_NAME_LENGTH + "~" + MAX_NAME_LENGTH + "자의 이름만 허용합니다.");
}
}

private void validatePattern(String name) {
if (!name.matches(FORMAT_NAME)) {
throw new IllegalArgumentException("이름은 알파벳과 숫자만 허용합니다.");
}
}

public String getName() {
return name;
}
}
25 changes: 25 additions & 0 deletions src/main/java/domain/StringParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain;

import java.util.Arrays;
import java.util.List;

public class StringParser {

public static List<String> splitByDelimiter(String before, String delimiter) {
try {
return Arrays.stream(before.split(delimiter, -1))
.map(String::trim)
.toList();
} catch (NullPointerException e) {
throw new IllegalArgumentException("null을 입력할 수 없습니다.");
}
}

public static int stringToInt(String before) {
try {
return Integer.parseInt(before);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("숫자를 입력해 주세요.");
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/error/ErrorHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package error;

import java.util.function.Supplier;

public class ErrorHandler {

private static final String ERROR_PREFIX = "[ERROR] ";

public <T> T readUntilNoError(Supplier<T> supplier) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
System.out.println(ERROR_PREFIX + e.getMessage());
return readUntilNoError(supplier);
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/strategy/ConnectionStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package strategy;

import domain.Connection;

public interface ConnectionStrategy {

Connection generateConnection();
}
Loading