예를 들어 다음과 같은 형식으로 입력이 주어진다고 가정해보자
(a,b)-(c,d)
우리는 이러한 사용자의 입력을 받아서, 형식에 따라 도메인 객체를 저장해야 한다.
public class Coordinate {
private int x;
private int y;
public Coordinate(int x, int y) {
this.x = x;
this.y = y;
}
}
이 경우, 문자열을 파싱하는 로직은 어디에 위치하는게 좋을까?
입력을 받는 UI 클래스에 추가
이 경우, 입력을 받는 메서드에서 바로 파싱 후 반환하면 된다.
-> 하지만 UI 클래스가 입력을 받는 역할과 파싱하는 역할을 동시에 맡게 되므로, 단일 책임 원칙에 어긋나게 된다.
도메인 클래스에 생성자로 추가
생성자로 입력 문자열을 받고, 이를 파싱해서 필드값으로 저장하는 방식이다.
-> 이 때에는 도메인 클래스가 입력 형식에 종속적이게 된다는 문제가 발생한다.
뷰와 컨트롤러 같은 웹단은 도메인 영역에 종속적이어도 괜찮지만, 도메인 영역은 웹단과 독립적이어야 한다.
입력형식과 같은 UI 영역은 언제든지 바뀔 수 있기 때문에, 도메인이 특정 입력 형식에 종속적인 것은 적절하지 않다.
파싱을 담당하는 별개의 클래스 작성
static 메서드를 통해 파싱을 담당하는 클래스를 작성하는 방식이다
-> 이 경우에는 기능 하나 때문에 추가적인 클래스를 작성하게 되어, 불필요하게 클래스 구조가 복잡해진다는 느낌을 받았다.
또한 이런 식으로 작성한 클래스는 결국 유틸리티 클래스인 것인데, 객체지향 관점에서 적절하지 않을 수 있다.
// 인터페이스 클래스
public interface CoordinatesParser {
String DELIMITER = "-";
String COMMA = ",";
boolean isValidPattern(String input);
Coordinates parseCoordinates(String input);
default void validateInput(String input) {
if (!isValidPattern(input)) {
throw new IllegalArgumentException("올바르지 않은 입력 형식입니다.");
}
}
default List<Coordinate> getCoordinateListFromInput(String input) {
return getCoordinateListFromValueList(getValueListFromInput(input));
}
private List<Integer> getValueListFromInput(String input) {
List<Integer> valueList = Arrays.stream(input.split(DELIMITER))
.map(substring -> substring.split(COMMA))
.flatMap(Arrays::stream)
.map(string -> string.replace("(", ""))
.map(string -> string.replace(")", ""))
.map(Integer::parseInt).collect(Collectors.toList());
return valueList;
}
private List<Coordinate> getCoordinateListFromValueList(List<Integer> valueList) {
List<Coordinate> coordinateList = new ArrayList<>();
for (int i = 0; i < valueList.size()/2; i++) {
coordinateList.add(
Coordinate.of(valueList.get(2*i), valueList.get(2*i + 1))
);
}
return coordinateList;
}
}
// 구현 클래스
public class LineParser implements CoordinatesParser {
private static final String PATTERN = CoordinatesParserType.LINE.getPattern();
private LineParser() {
}
public static LineParser newInstance() {
return new LineParser();
}
@Override
public boolean isValidPattern(String input) {
return input.matches(PATTERN);
}
@Override
public Line parseCoordinates(String input) {
validateInput(input);
return Line.of(getCoordinateListFromInput(input));
}
}