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

[LBP] 김준 사다리 5단계 제출합니다. #43

Open
wants to merge 10 commits into
base: kjoon418
Choose a base branch
from
10 changes: 7 additions & 3 deletions src/main/java/LadderApplication.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import controller.LadderController;

public class LadderApplication {
public static void main(String[] args) {
LadderController ladderController = LadderController.getInstance();

ladderController.run();
public static void main(String[] args) {
try {
LadderController ladderController = LadderController.getInstance();
ladderController.run();
} catch (Exception e) {
e.printStackTrace();
}
}

}
88 changes: 80 additions & 8 deletions src/main/java/controller/LadderController.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package controller;

import dto.LadderResultDto;
import dto.LineDto;
import model.Ladder;
import model.Line;
import model.*;

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.

IntelliJ의 자동 임포트 기능을 활용하다보니 *가 사용된 것 같습니다.
IntelliJ가 제공해준 import 구문을 굳이 바꿀 필요 없다 생각해서 의식하지 않았는데, 이에 관한 리뷰어님의 의견이 궁금합니다.

리뷰어님은 *의 사용을 지양하시나요?

Choose a reason for hiding this comment

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

저는 어떤 클래스가 import 되는지 명시를 해주는 게 다른 개발자가 코드를 읽을 때 "아! 이 도메인이 사용되었구나." 하고 빠르게 이해하는 데 도움이 된다고 생각해요.

그리고 대부분의 컴파일러에서 사용되지 않는 import는 자동으로 최적화되지만 일부 경우에는 불필요한 클래스가 로드될 수 있기 때문에 *사용을 지양하는 편이에요!

하지만 무조건 *을 사용하지 않는 것은 아니고, 상수나 Math 같은 유틸 클래스의 메서드를 여러 개 사용할 경우에는 사용해도 된다고 생각합니다 😄

Copy link
Author

Choose a reason for hiding this comment

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

조언 감사합니다!
앞으로 코드를 작성할 때 *가 과하게 사용되진 않았는지 확인하도록 하겠습니다 😄

import view.LadderInputView;
import view.LadderOutputView;

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

public class LadderController {
private static final int DEFAULT_LADDER_HEIGHT = 4;
private static final int DEFAULT_LADDER_WIDTH = 4;

private static final String PRINT_EVERY_RESULT = "all";

private static final LadderController ladderController = new LadderController();

private final LadderOutputView ladderOutputView = LadderOutputView.getInstance();
private static final LadderOutputView ladderOutputView = LadderOutputView.getInstance();
private static final LadderInputView ladderInputView = LadderInputView.getInstance();

private LadderController() {
}
Expand All @@ -23,14 +27,82 @@ public static LadderController getInstance() {
}

public void run() {
Ladder ladder = new Ladder(DEFAULT_LADDER_WIDTH, DEFAULT_LADDER_HEIGHT);
List<Line> lines = ladder.getLines();
LadderUsers ladderUsers = getLadderUsers();
String[] resultValues = ladderInputView.getResultValues();
int height = ladderInputView.getHeight();

Ladder ladder = new Ladder(ladderUsers.size(), height);
LadderResultCalculator ladderResultCalculator = new LadderResultCalculator(ladderUsers, resultValues);
printLadderShape(ladder, ladderUsers, resultValues);

printLadderResultUntilPrintEveryResult(ladder, ladderResultCalculator, ladderUsers);
}

private LadderUsers getLadderUsers() {
String[] names = ladderInputView.getNames();

List<LadderUser> ladderUsers = Arrays.stream(names)
.map(LadderUser::new)
.toList();

ladderOutputView.printResultHeader();
return new LadderUsers(ladderUsers);
}

private void printLadderShape(Ladder ladder, LadderUsers ladderUsers, String[] resultValues) {
ladderOutputView.printLadderResultHeader();
ladderOutputView.printNames(ladderUsers.getNames());

List<Line> lines = ladder.getLines();
for (Line line : lines) {
LineDto lineDto = LineDto.from(line);
ladderOutputView.printLine(lineDto);
}

ladderOutputView.printResultValues(resultValues);
}

private void printLadderResultUntilPrintEveryResult(
Ladder ladder,
LadderResultCalculator ladderResultCalculator,
LadderUsers ladderUsers
) {
String targetName = ladderInputView.getTargetName();

while (shouldPrintSingleResult(targetName)) {
printSingleLadderResult(targetName, ladder, ladderResultCalculator);
targetName = ladderInputView.getTargetName();
}

printEveryLadderResult(ladder, ladderResultCalculator, ladderUsers);
}

private boolean shouldPrintSingleResult(String targetName) {
return !targetName.equals(PRINT_EVERY_RESULT);
}

private void printSingleLadderResult(
String targetName,
Ladder ladder,
LadderResultCalculator ladderResultCalculator
) {
String result = ladderResultCalculator.calculate(targetName, ladder);

ladderOutputView.printLadderResult(result);
}

private void printEveryLadderResult(
Ladder ladder,
LadderResultCalculator ladderResultCalculator,
LadderUsers ladderUsers
) {
List<LadderResultDto> ladderResultDtos = new ArrayList<>();

for (String name : ladderUsers.getNames()) {
String result = ladderResultCalculator.calculate(name, ladder);
ladderResultDtos.add(new LadderResultDto(name, result));
}

ladderOutputView.printLadderResults(ladderResultDtos);
}

}
4 changes: 4 additions & 0 deletions src/main/java/dto/LadderResultDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package dto;

public record LadderResultDto(String name, String resultValue) {

Choose a reason for hiding this comment

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

record 사용 👍🏻

}
10 changes: 5 additions & 5 deletions src/main/java/dto/LineDto.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package dto;

import model.Line;
import model.Link;
import model.LinkStatus;

import java.util.List;

import static model.LinkStatus.PRESENT;

public class LineDto {

private final List<Boolean> linkExistCollection;

private LineDto(List<Boolean> linkExistCollection) {
Expand All @@ -15,15 +16,14 @@ private LineDto(List<Boolean> linkExistCollection) {

public static LineDto from(Line line) {
List<Boolean> linkExistCollection = line.getLinks().stream()
.map(Link::getLinkstatus)
.map(LinkStatus::isPresent)
.map(link -> link.getLinkstatus() == PRESENT)
.toList();

return new LineDto(linkExistCollection);
}

public List<Boolean> getLinkExistCollection() {
return List.copyOf(linkExistCollection);
return linkExistCollection;
}

}
98 changes: 98 additions & 0 deletions src/main/java/model/DetachedRandomLinksGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package model;

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

import static model.LinkStatus.PRESENT;

public class DetachedRandomLinksGenerator implements LinksGenerator {

private static final Random random = new Random();

private final int linksSize;

public DetachedRandomLinksGenerator(int linksSize) {
this.linksSize = linksSize;
}

public List<Link> generate() {
List<Link> links = getUndefinedLinks(linksSize);
resolveUndefinedLinks(links);

return Collections.unmodifiableList(links);
}

private List<Link> getUndefinedLinks(int linksSize) {
List<Link> links = new ArrayList<>();
for (int i = 0; i < linksSize; i++) {
links.add(Link.getUndefinedLink());
}

return links;
}

private void resolveUndefinedLinks(List<Link> links) {
int index = getRandomStartIndex(links);

while (containsUndefined(links)) {
boolean connectDecider = getConnectDecider();
boolean connectable = isConnectable(index, links);

links.set(index, Link.getDefinedLink(connectDecider, connectable));

index = getNextIndex(index, links);
}
}

private int getRandomStartIndex(List<Link> links) {
return random.nextInt(links.size());
}

private boolean containsUndefined(List<Link> links) {
return links.stream()
.anyMatch(Link::isUndefined);
}

private boolean getConnectDecider() {
return random.nextBoolean();
}

private boolean isConnectable(int index, List<Link> links) {
return isLeftNotPresent(index, links) && isRightNotPresent(index, links);
}

private boolean isLeftNotPresent(int index, List<Link> links) {
if (isFirstIndex(index)) {
return true;
}

Link leftLink = links.get(index - 1);

return leftLink.getLinkstatus() != PRESENT;
}

private boolean isRightNotPresent(int index, List<Link> links) {
if (isLastIndex(index, links)) {
return true;
}

Link rightLink = links.get(index + 1);

return rightLink.getLinkstatus() != PRESENT;
}

private boolean isFirstIndex(int index) {
return index == 0;
}

private boolean isLastIndex(int index, List<Link> links) {
return index == links.size() - 1;
}

private int getNextIndex(int index, List<Link> linkStatuses) {
return (index + 1) % linkStatuses.size();
}
Comment on lines +66 to +96

Choose a reason for hiding this comment

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

인덱스 관련 로직이 Line 객체에서도 유사하게 사용되는 것 같아요.
혹시 이렇게 따로 작성하신 이유가 있을까요?

Copy link
Author

@kjoon418 kjoon418 Mar 21, 2025

Choose a reason for hiding this comment

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

맞습니다...! 유사한 로직이 LineDetachedRandomLinksGenerator 모두에 존재하고 있습니다.
제가 우선 현재처럼 구현하게 된 이유는 다음과 같습니다.

|-----|-----|이라는 라인이 있을 때, 해당 라인은 총 3개의 Point(|)와 2개의 Link(-----)를 갖습니다.
그렇기 때문에 마지막 Point인지 검사하기 위해선 links.size()와 같은지 검사해야 했고,
(Link)의 마지막 Index인지 검사하기 위해선 links.size() - 1과 같은지 검사해야 했습니다.

이 점 때문에 메서드 이름을 is...Index()is...Point()로 구분해 작성했지만, 제 코드를 읽는 다른 개발자들에게 직관적이지 않을 것 같다는 점이 계속 마음에 걸렸습니다.

전략 패턴을 처음 사용해보면서 놓친 것이 많은 것 같은데, 오래 고민한 후 해결해 보도록 하겠습니다!


}
41 changes: 37 additions & 4 deletions src/main/java/model/Ladder.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,52 @@
import java.util.List;

public class Ladder {

private static final int MINIMUM_WIDTH = 2;
private static final int MINIMUM_HEIGHT = 1;

private final List<Line> lines;

public Ladder(int width, int height) {
validateSize(width, height);

this.lines = createLines(width, height);
}

public List<Line> getLines() {
return lines;
}

public int getEndPoint(int startPoint) {
int point = startPoint;

for (Line line : lines) {
point = line.getNextPoint(point);
}

return point;
}

private List<Line> createLines(int width, int height) {
int linksSize = width - 1;
DetachedRandomLinksGenerator linksGenerator = new DetachedRandomLinksGenerator(linksSize);

List<Line> lines = new ArrayList<>();
for (int i = 0; i < height; i++) {
lines.add(new Line(width));
Line line = new Line(linksGenerator);
lines.add(line);
}

this.lines = Collections.unmodifiableList(lines);
return Collections.unmodifiableList(lines);

Choose a reason for hiding this comment

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

준님이 이번 미션을 하면서 어떤 기준으로 unmodifiableList()copyOf()를 선택하셨는지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

정말 부끄럽게도 unmodifiable...()copyOf()의 특징을 반대로 이해했습니다. 😭
원본 컬렉션과의 연결을 끊고자 unmodifiableList()를 사용한 건데, 반대로 사용했군요...!

아래는 저의 기준입니다...!

저는 둘의 차이점을 공부하며, unmodifiable...()는 기존 컬렉션과 똑같은 참조를 반환한다라는 점에 주목했습니다.
'넘겨받은 불변 컬렉션이 외부로 인해 변할 수 있다'는 점은 상당히 위험해 보였고, 의도적으로 그렇게 동작하도록 만들 경우가 아니면 unmodifiable...()를 사용하지 않아야겠다 판단했습니다.

}

public List<Line> getLines() {
return lines;
private void validateSize(int width, int height) {
if (width < MINIMUM_WIDTH) {
throw new IllegalArgumentException("사다리의 너비는 " + MINIMUM_WIDTH + "보다 짧을 수 없습니다. 전달된 값: " + width);
}
if (height < MINIMUM_HEIGHT) {
throw new IllegalArgumentException("사다리의 높이는 " + MINIMUM_HEIGHT + "보다 짧을 수 없습니다. 전달된 값: " + height);
}
Comment on lines +51 to +53

Choose a reason for hiding this comment

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

높이 입력값이 1일 때도 사다리가 생성이 되나요? 🧐

Copy link
Author

Choose a reason for hiding this comment

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

image
네이버 사다리 게임을 실행해 보았더니, 위 사진과 같은 케이스도 발생하는 것을 확인할 수 있었습니다.

사다리 결과

    1     2   
    |-----|
    3     4   

그에 따라 위와 같이 높이가 1인 사다리도 문제 없다고 생각해 허용했는데, 다시 생각해보니 높이가 1인 사다리는 어색한 것 같네요.
사다리 높이가 2일 때 부터 생성되도록 수정하도록 하겠습니다!

}

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

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

public class LadderResultCalculator {

private final LadderUsers ladderUsers;
private final List<String> resultValues;

public LadderResultCalculator(LadderUsers ladderUsers, String[] resultValues) {
validateSize(ladderUsers, resultValues);

this.ladderUsers = ladderUsers;
this.resultValues = Arrays.stream(resultValues)
.toList();
}

public String calculate(String name, Ladder ladder) {
int index = ladderUsers.findIndexOfUserByName(name)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이름입니다."));
int endPoint = ladder.getEndPoint(index);

return findResultByIndex(endPoint);
}

private void validateSize(LadderUsers ladderUsers, String[] resultValues) {
if (ladderUsers.size() != resultValues.length) {
throw new IllegalArgumentException("참여자의 수와 실행 결과의 수가 일치하지 않습니다.");
}
}

private String findResultByIndex(int index) {
return resultValues.get(index);
}

}
Loading