Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
75aecbb
[step2] docs : 구현 기능 목록(초벌) 작성
Integerous Jul 2, 2019
2cbabd1
[step2] feat : 게임참여자(Player) 객체 생성
Integerous Jul 2, 2019
6aac283
[step2] feat : Player 이름 예외 처리
Integerous Jul 2, 2019
c44d5e0
[step2] refactor : 예외처리 메서드 분리 및 상수 사용
Integerous Jul 2, 2019
6dfa703
[step2] feat : Players 객체 생성
Integerous Jul 2, 2019
e6630ac
[step2] refactor : 입력된 Players 이름에 공백 제거, 상수 사용
Integerous Jul 2, 2019
b6af3e3
[step2] feat : 플레이어 인원수 예외처리
Integerous Jul 2, 2019
7a883ad
[step2] feat : 사다리의 열(Line) 객체 생성
Integerous Jul 3, 2019
8f016c1
[step2] feat : Line의 bar를 (플레이어 수 - 1) 만큼 bar가 연속되지 않도록 랜덤으로 생성
Integerous Jul 3, 2019
595047f
[step2] refactor : Line 생성하는 메서드에서 Bar 생성하는 메서드 분리
Integerous Jul 3, 2019
5754205
[step2] feat : bar 생성 인터페이스와 랜덤 생성 구현체 추가
Integerous Jul 3, 2019
d2c4d88
[step2] refactor : Line 생성 메서드를 LineMaker 객체로 분리
Integerous Jul 3, 2019
51fdfd5
[step2] feat : 사다리(Ladder) 객체 및 생성 메서드 추가
Integerous Jul 3, 2019
db7fd55
[step2] refactor : 사다리 생성에 스트림과 람다식 사용
Integerous Jul 3, 2019
31544a8
[step2] feat : 사다리 높이(Height) 객체 추가 및 예외 처리
Integerous Jul 3, 2019
7570f85
[step2] refactor : 원시값 포장
Integerous Jul 3, 2019
54c6bef
[step2] refactor : 패키지 구분, View 클래스 생성
Integerous Jul 3, 2019
23729f3
[step2] feat : 참여자 이름과 사다리 높이를 입력받는 InputView 메서드 추가
Integerous Jul 3, 2019
9b35290
[step2] refactor : Ladder 정적팩토리 메서드의 파라미터 순서를 입력 받는 순서로 변경
Integerous Jul 3, 2019
a98d95c
[step2] feat : 참여자 이름과 사다리를 출력하는 OutputView 메서드 추가(미완성)
Integerous Jul 3, 2019
36531ae
[step2] refactor : 참여자 이름과 사다리를 출력하는 OutputView 메서드 개선(미완성)
Integerous Jul 3, 2019
ea16228
[step2] feat : 각 라인의 마지막 기둥 추가
Jul 4, 2019
d48e3c2
[step2] refactor : 사다리 표현 문자들을 상수로 변환
Jul 4, 2019
0184b02
[step2] docs : 구현 기능 목록 업데이트
Jul 4, 2019
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
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,30 @@
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/nextstep-step/nextstep-docs/tree/master/codereview)
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/nextstep-step/nextstep-docs/tree/master/codereview)


## 구현 기능 목록

- [x] 게임참가자(플레이어)의 이름을 입력받는다.
- [x] 입력된 이름들이 플레이어가 된다.
- [x] 플레이어의 이름은 (,)로 구분된다.
- [x] 예외 처리: (,) 사이의 공백 제거
- [x] 플레이어의 이름은 최대 5글자이다.
- [x] 예외 처리: 5글자가 넘거나 공백일 경우
- [x] 플레이어는 최소 2명 있어야 한다. (사다리의 다리는 최소 2개 이상이므로)
- [x] 예외 처리: 플레이어가 2명 미만일 경우
- [x] 사다리 높이를 입력받는다.
- [x] 예외 처리: 사다리 높이가 1 미만일 경우
- [x] 플레이어들을 출력한다.
- [x] 플레이어 이름
- [x] 플레이어 이름이 5글자 미만일 경우 부족한 만큼 왼쪽으로 공백을 채운다.
- [x] 사다리를 출력한다.
- [x] 사다리의 열을 생성한다.
- [x] 각 열은 기둥과 바(bar)로 이루어져있다.
- [x] 기둥 사이를 잇는 바를 랜덤으로 생성한다.
- [x] 바는 연속될 수 없다.
- [x] 바는 '-' 5개로 이루어져있다.
- [x] 입력받은 높이만큼 열을 추가한다.


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

import ladder.domain.Height;
import ladder.domain.Ladder;
import ladder.domain.Players;
import ladder.view.InputView;
import ladder.view.OutputView;

public class Application {

public static void main(String[] args) {
Players players = Players.of(InputView.askPlayers());
Height height = Height.from(InputView.askHeight());

Ladder ladder = Ladder.from(players, height);

OutputView.printResult(players, ladder);
}
}
6 changes: 6 additions & 0 deletions src/main/java/ladder/domain/BarGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package ladder.domain;

public interface BarGenerator {
Copy link

Choose a reason for hiding this comment

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

BarGenerator 인터페이스 👍 이런 형태의 인터페이스를 함수형 인터페이스라고 부른답니다. @FunctionalInterface가 무엇인지 찾아보고 공부해 보세요.

Copy link
Author

@Integerous Integerous Jul 5, 2019

Choose a reason for hiding this comment

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

@FunctionalInterface 어노테이션을 붙이면 개발자들이 함수형 인터페이스라는 것을 알 수 있고, 이 인터페이스가 오직 하나의 추상메서드만 가지는지 컴파일러가 체크할 수 있게 한다는 점을 알게 되었습니다-! 함수형 인터페이스 자체에 대해서도 더 깊게 공부해보겠습니다. 감사합니다! 👍


boolean generateBar();
}
13 changes: 13 additions & 0 deletions src/main/java/ladder/domain/BarGeneratorImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ladder.domain;

import java.util.Random;

public class BarGeneratorImpl implements BarGenerator{
Copy link

Choose a reason for hiding this comment

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

클래스 이름의 'Impl'은 의미가 없습니다. 아래의 글을 읽고 코드에 반영하면 어떨까요?
https://octoperf.com/blog/2016/10/27/impl-classes-are-evil

Copy link
Author

@Integerous Integerous Jul 5, 2019

Choose a reason for hiding this comment

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

유익한 글 공유 감사합니다! 무의식적으로 Impl을 붙여왔었는데 잘못된 습관이었네요!

질문이 있습니다. 만약 비즈니스 로직이 BarGeneratorImpl 처럼 랜덤으로 Bars를 생성하는 경우 외에 다른 방법으로 Bars를 생성할 일이 없는 경우에도 인터페이스로 구현하는 것이 맞는지 궁금합니다.. 인터페이스로 빼야 될 것 같은 느낌(?) 때문에 인터페이스로 구현했는데 공유주신 글 내용중에

there is no point of creating an interface for a service which has only a single known implementation

이런 내용이 있어서 BarGenerator를 인터페이스로 만들 필요가 있었는지 의문이 생겼습니다. 제가 인터페이스에 익숙하지 않아서 더더욱 헷갈리네요..ㅠ

Copy link

Choose a reason for hiding this comment

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

질문에 대한 답변은 Slack을 통해 남겼어요. 👍


private Random random = new Random();

@Override
public boolean generateBar() {
return random.nextBoolean();
}
}
23 changes: 23 additions & 0 deletions src/main/java/ladder/domain/Height.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ladder.domain;

public class Height {
static final String ALERT_MINIMUM_LADDER_HEIGHT = "사다리의 최소 높이는 1입니다.";
static final int MINIMUM_LADDER_HEIGHT = 1;

private final int height;

private Height(int inputHeight) {
if (inputHeight < MINIMUM_LADDER_HEIGHT) {
throw new IllegalArgumentException(ALERT_MINIMUM_LADDER_HEIGHT);
}
this.height = inputHeight;
}

public static Height from(int inputHeight) {
return new Height(inputHeight);
}

public int getHeight() {
return height;
}
}
26 changes: 26 additions & 0 deletions src/main/java/ladder/domain/Ladder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ladder.domain;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Ladder {

private List<Line> lines;

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

public static Ladder from(Players players, Height height) {
return new Ladder(IntStream
.range(0, height.getHeight())
.mapToObj((integer) -> Line.from(players.numberOfPlayers()))
.collect(Collectors.toList()));
}

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

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

public class Line {

private List<Boolean> bars;
Copy link

Choose a reason for hiding this comment

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

Boolean은 클래스로 포장하여 사용해 볼 수 있겠어요.


private Line(List<Boolean> bars) {
this.bars = new ArrayList<>(bars);
Copy link

Choose a reason for hiding this comment

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

새로운 컬렉션에 담아 외부에 의한 변조를 막는 것은 습관처럼 사용하는 것이 좋습니다. 👍

}

public static Line from(int numberOfPlayers) {
LineMaker lineMaker = new LineMaker();
return new Line(lineMaker.generateBars(numberOfPlayers));
}

public List<Boolean> getBars() {
return Collections.unmodifiableList(bars);
}
}
32 changes: 32 additions & 0 deletions src/main/java/ladder/domain/LineMaker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ladder.domain;

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

class LineMaker {

private List<Boolean> randomBars = new ArrayList<>();
private BarGeneratorImpl barGenerator = new BarGeneratorImpl();

List<Boolean> generateBars(int numberOfPlayers) {
Copy link

Choose a reason for hiding this comment

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

이 메서드는 두 번 이상 사용할 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

미처 생각하지 못한 부분인데 피드백주셔서 감사합니다!!

제가 이해한 바는, 이 메서드를 한 번 사용하고 나면 LineMaker 클래스의 인스턴스 변수 randomBars에 데이터가 박혀있기 때문에 다시 사용하면, 이전에 사용했던 randomBars에 bars를 추가하게 되므로 재사용할 수 없다고 이해했는데 맞을까요..?

그래서

class LineMaker {

private List<Bar> randomBars;

List<Bar> generateBars(int numberOfPlayers) {
        this.randomBars = new ArrayList<>(); // 이 부분 추가
        generateFirstBar();
        generateMiddleBars(numberOfPlayers);
        generateLastBar();
        return randomBars;
    }
...
}

위와 같이 generateBars메서드가 호출되면 randomBars가 초기화되도록 바꾸었는데 올바른 방법인지 궁금합니다..!!

Copy link

Choose a reason for hiding this comment

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

네, 맞습니다. 👍

randomBars.add(barGenerator.generateBar());
for (int i = 1; i < numberOfPlayers - 1; i++) {
Copy link

Choose a reason for hiding this comment

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

numberOfPlayers - 1이 왜 필요한지 메서드 분리를 통해 설명하면 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

for문은 첫번째 bar와 마지막 bar를 제외한 가운데 bars를 생성하는 부분이라서 generateMiddleBars()라는 메서드로 분리했습니다. 그리고 나머지 부분도 generateFirstBar() 메서드와 generateLastBar()메서드로 분리해서 generateBars() 메서드가 가지고 있던 역할을 위임했습니다.

혹시 피드백주신 부분에 대해 제가 이해한 바가 맞을까요?

Copy link

Choose a reason for hiding this comment

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

네, 맞습니다. 👍

boolean previousBarExist = randomBars.get(i - 1);
addNextBar(previousBarExist);
}
addBlankBar();
return randomBars;
}

private void addNextBar(boolean previousBarExist) {
if (previousBarExist) {
addBlankBar();
return;
}
randomBars.add(barGenerator.generateBar());
}

private void addBlankBar() {
randomBars.add(false);
}
}
38 changes: 38 additions & 0 deletions src/main/java/ladder/domain/Player.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ladder.domain;

public class Player {
private static final int MAXIMUM_NAME_LENGTH = 5;
private static final String ALERT_EXCEED_OF_NAME_LENGTH = "이름은 최대 5글자까지 가능합니다.";
private static final String ALERT_EMPTY_NAME = "이름은 최소 1글자 이상 입력하세요.";

private final String name;

private Player(String name) {
validationPlayerName(name);
this.name = name;
}

public static Player from(String name) {
return new Player(name);
}

private void validationPlayerName(String name) {
if (name.length() > MAXIMUM_NAME_LENGTH) {
throw new IllegalArgumentException(ALERT_EXCEED_OF_NAME_LENGTH);
}
if ("".equals(name)) {
throw new IllegalArgumentException(ALERT_EMPTY_NAME);
}
}

public String getName() {
return name;
}

@Override
public String toString() {
return "Player{" +
"name='" + name + '\'' +
'}';
}
}
36 changes: 36 additions & 0 deletions src/main/java/ladder/domain/Players.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ladder.domain;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Players {
private static final String NAME_SEPARATOR = ",";
private static final int MINIMUM_NUMBER_OF_PLAYERS = 2;
private static final String ALERT_SHORTAGE_OF_NUMBER_OF_PLAYERS = "플레이어는 최소 2명이 필요합니다.";

private final List<Player> players;

private Players(List<Player> players) {
if (players.size() < MINIMUM_NUMBER_OF_PLAYERS) {
throw new IllegalArgumentException(ALERT_SHORTAGE_OF_NUMBER_OF_PLAYERS);
}
this.players = players;
}

public static Players of(String inputNames) {
return new Players(Arrays.stream(inputNames.trim().split(NAME_SEPARATOR))
.map(String::trim)
.map(Player::from)
.collect(Collectors.toList()));
}

public int numberOfPlayers() {
return players.size();
}

public List<Player> getPlayers() {
return Collections.unmodifiableList(players);
}
}
25 changes: 25 additions & 0 deletions src/main/java/ladder/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ladder.view;

import java.util.Scanner;

public class InputView {
private static final String MESSAGE_FOR_INPUT_PLAYER_NAMES = "참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)";
private static final String MESSAGE_FOR_MAXIMUM_LADDER_HEIGHT = "최대 사다리 높이는 몇 개인가요?";

private static Scanner scanner = new Scanner(System.in);

public static String askPlayers() {
System.out.println(MESSAGE_FOR_INPUT_PLAYER_NAMES);
return scanner.nextLine();
}

public static int askHeight() {
printEmptyLine();
System.out.println(MESSAGE_FOR_MAXIMUM_LADDER_HEIGHT);
return scanner.nextInt();
}

static void printEmptyLine() {
System.out.println();
}
}
70 changes: 70 additions & 0 deletions src/main/java/ladder/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package ladder.view;

import ladder.domain.Ladder;
import ladder.domain.Line;
import ladder.domain.Player;
import ladder.domain.Players;

import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static ladder.view.InputView.printEmptyLine;
Copy link

Choose a reason for hiding this comment

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

InputViewOutputView는 서로 다른 View라고 생각하고 분리하면 어떨까요?


public class OutputView {
private static final String MESSAGE_RESULT_TITLE = "실행결과";
private static final String EMPTY_SPACE = " ";
private static final String BAR = "-----";
private static final String COLUMN = "|";
private static final String BLANK_TO_FILL_THE_NAME_SPACE = " ";
private static final int SPACE_FOR_NAME = 5;

public static void printResult(Players players, Ladder ladder) {
printResultTitle();
printPlayers(players);
printLadder(ladder);
}

private static void printResultTitle() {
printEmptyLine();
System.out.println(MESSAGE_RESULT_TITLE);
printEmptyLine();
}

private static void printPlayers(Players players) {
players.getPlayers().stream()
.map(OutputView::adjustNameLength)
Copy link

Choose a reason for hiding this comment

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

String#format(String, Object...)을 사용해 보면 어떨까요?

.forEach(System.out::print);
printEmptyLine();
}

private static String adjustNameLength(Player player) {
String name = player.getName();
int spaceForBlank = SPACE_FOR_NAME - name.length();

return IntStream.rangeClosed(0, spaceForBlank)
.mapToObj((integer) -> BLANK_TO_FILL_THE_NAME_SPACE)
.collect(Collectors.joining())
.concat(name);
}

private static void printLadder(Ladder ladder) {
ladder.getLines().forEach(OutputView::printLine);
}

private static void printLine(Line line) {
System.out.print(EMPTY_SPACE);
line.getBars().stream()
.map(OutputView::printBars)
.forEach(System.out::print);
printEmptyLine();
}

private static String printBars(Boolean bar) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(COLUMN);
if (bar) {
return stringBuilder.append(BAR).toString();
}
return stringBuilder.append(EMPTY_SPACE).toString();
}
}
17 changes: 17 additions & 0 deletions src/test/java/ladder/domain/HeightTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ladder.domain;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

public class HeightTest {
@Test
void 입력받은_사다리의_높이가_1보다_작을_경우_예외가_발생한다() {
int inputHeight = Height.MINIMUM_LADDER_HEIGHT - 1;

assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> {
Height.from(inputHeight);
}).withMessage(Height.ALERT_MINIMUM_LADDER_HEIGHT);
}
}
15 changes: 15 additions & 0 deletions src/test/java/ladder/domain/LadderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ladder.domain;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class LadderTest {
@Test
void 입력된_사다리의_높이만큼_Line을_생성하여_사다리를_만든다() {
int inputHeight = 5;
Ladder ladder = Ladder.from(Players.of("test1,test2,test3"), Height.from(inputHeight));

assertThat(ladder.getLines().size()).isEqualTo(inputHeight);
}
}
Loading