diff --git a/build.gradle b/build.gradle index 8172fb73..fd548118 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'eclipse' group = 'camp.nextstep' version = '1.0.0' -sourceCompatibility = '1.8' +sourceCompatibility = '11' repositories { mavenCentral() diff --git a/src/main/java/coordinate/CoordinateMain.java b/src/main/java/coordinate/CoordinateMain.java new file mode 100644 index 00000000..fc909d1c --- /dev/null +++ b/src/main/java/coordinate/CoordinateMain.java @@ -0,0 +1,20 @@ +package coordinate; + +import coordinate.controller.CoordinateController; +import coordinate.view.InputConsoleView; +import coordinate.view.InputView; +import coordinate.view.OutputConsoleView; +import coordinate.view.OutputView; + +import java.util.Scanner; + +public class CoordinateMain { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + InputView inputView = new InputConsoleView(scanner); + OutputView outputView = new OutputConsoleView(); + CoordinateController coordinateController = new CoordinateController(inputView, outputView); + coordinateController.run(); + scanner.close(); + } +} diff --git a/src/main/java/coordinate/controller/CoordinateController.java b/src/main/java/coordinate/controller/CoordinateController.java new file mode 100644 index 00000000..42ddaaa9 --- /dev/null +++ b/src/main/java/coordinate/controller/CoordinateController.java @@ -0,0 +1,49 @@ +package coordinate.controller; + +import coordinate.domain.Figure; +import coordinate.domain.FigureFactory; +import coordinate.domain.Line; +import coordinate.domain.StraightLine; +import coordinate.domain.vo.Point; +import coordinate.view.InputView; +import coordinate.view.OutputView; + +import java.util.List; + +public class CoordinateController { + + private static final int NUMBER_OF_LINE = 2; + + private final InputView inputView; + private final OutputView outputView; + + public CoordinateController(final InputView inputView, final OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + List points = inputView.input(); + + if (isLineNumberOf(points)) { + printLine(points); + return; + } + + printFigure(points); + } + + private boolean isLineNumberOf(final List points) { + return points.size() == NUMBER_OF_LINE; + } + + private void printLine(final List points) { + Line line = new StraightLine(points); + outputView.printLine(line); + } + + private void printFigure(final List points) { + Figure figure = FigureFactory.create(points); + outputView.printFigure(figure); + } +} diff --git a/src/main/java/coordinate/domain/Figure.java b/src/main/java/coordinate/domain/Figure.java new file mode 100644 index 00000000..2ba5c5de --- /dev/null +++ b/src/main/java/coordinate/domain/Figure.java @@ -0,0 +1,7 @@ +package coordinate.domain; + +public interface Figure { + double area(); + + String figureType(); +} diff --git a/src/main/java/coordinate/domain/FigureFactory.java b/src/main/java/coordinate/domain/FigureFactory.java new file mode 100644 index 00000000..257f2f9f --- /dev/null +++ b/src/main/java/coordinate/domain/FigureFactory.java @@ -0,0 +1,38 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class FigureFactory { + + private static final String INVALID_NUMBER_OF_POINTS_MESSAGE = "유효하지 않은 점의 개수입니다"; + private static final int NUMBER_OF_TRIANGLE_POINTS = 3; + private static final int NUMBER_OF_RECTANGLE_POINTS = 4; + + private static final Map, Figure>> factory = new HashMap<>(); + + static { + factory.put(NUMBER_OF_TRIANGLE_POINTS, Triangle::new); + factory.put(NUMBER_OF_RECTANGLE_POINTS, Rectangle::new); + } + + private FigureFactory() { + + } + + public static Figure create(final List points) { + int numberOfPoints = points.size(); + validateNumberOfPoints(numberOfPoints); + return factory.get(numberOfPoints).apply(points); + } + + private static void validateNumberOfPoints(final int numberOfPoints) { + if (numberOfPoints < NUMBER_OF_TRIANGLE_POINTS || numberOfPoints > NUMBER_OF_RECTANGLE_POINTS) { + throw new IllegalArgumentException(INVALID_NUMBER_OF_POINTS_MESSAGE); + } + } +} diff --git a/src/main/java/coordinate/domain/Line.java b/src/main/java/coordinate/domain/Line.java new file mode 100644 index 00000000..e5a472e1 --- /dev/null +++ b/src/main/java/coordinate/domain/Line.java @@ -0,0 +1,6 @@ +package coordinate.domain; + +@FunctionalInterface +public interface Line { + double length(); +} diff --git a/src/main/java/coordinate/domain/Rectangle.java b/src/main/java/coordinate/domain/Rectangle.java new file mode 100644 index 00000000..5c61dd85 --- /dev/null +++ b/src/main/java/coordinate/domain/Rectangle.java @@ -0,0 +1,112 @@ +package coordinate.domain; + +import coordinate.domain.vo.Coordinate; +import coordinate.domain.vo.Point; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static java.util.Arrays.asList; + +public class Rectangle implements Figure { + + private static final int NUMBER_OF_RECTANGLE_POINTS = 4; + private static final int NUMBER_OF_DUPLICATED_COORDINATE = 2; + + private static final String INVALID_NUMBER_OF_RECTANGLE_POINTS_MESSAGE = "사각형의 점은 4개여야 합니다"; + private static final String INVALID_RECTANGLE_MESSAGE = "직사각형이 아닙니다"; + private static final String DUPLICATE_POINTS_EXCEPTION_MESSAGE = "점 사이에 중복이 존재합니다"; + + private final List points; + + public Rectangle(final List points) { + validateNumberOf(points); + validateDuplicateOf(points); + validateIsRectangle(points); + this.points = points; + } + + private void validateNumberOf(final List points) { + if (points.size() != NUMBER_OF_RECTANGLE_POINTS) { + throw new IllegalArgumentException(INVALID_NUMBER_OF_RECTANGLE_POINTS_MESSAGE); + } + } + + private void validateDuplicateOf(final List points) { + Set distinctPoints = new HashSet<>(points); + + if (distinctPoints.size() < points.size()) { + throw new IllegalArgumentException(DUPLICATE_POINTS_EXCEPTION_MESSAGE); + } + } + + private void validateIsRectangle(final List points) { + Set duplicatedRemovedXCoordinate = removeDuplicateCoordinate(points, Point::x); + Set duplicatedRemovedYCoordinate = removeDuplicateCoordinate(points, Point::y); + + if (hasTwo(duplicatedRemovedXCoordinate) && hasTwo(duplicatedRemovedYCoordinate)) { + throw new IllegalArgumentException(INVALID_RECTANGLE_MESSAGE); + } + } + + private Set removeDuplicateCoordinate(final List points, + final Function extractedFunction) { + return points.stream() + .map(extractedFunction) + .collect(Collectors.toSet()); + } + + private boolean hasTwo(final Set duplicatedRemovedCoordinate) { + return duplicatedRemovedCoordinate.size() != NUMBER_OF_DUPLICATED_COORDINATE; + } + + private List points() { + return points; + } + + @Override + public String figureType() { + return "사각형"; + } + + @Override + public double area() { + Point point = points.get(0); + Point sameXCoordinatePoint = getSameCoordinatePoint(point::hasSameXCoordinate); + Point sameYCoordinatePoint = getSameCoordinatePoint(point::hasSameYCoordinate); + + Line width = new StraightLine(asList(point, sameXCoordinatePoint)); + Line height = new StraightLine(asList(point, sameYCoordinatePoint)); + + return width.length() * height.length(); + } + + private Point getSameCoordinatePoint(final Predicate hasSameCoordinatePredicate) { + int startIndex = 1; + + return IntStream.range(startIndex, points.size()) + .mapToObj(points::get) + .filter(hasSameCoordinatePredicate) + .findAny() + .orElseThrow(() -> new IllegalStateException(INVALID_RECTANGLE_MESSAGE)); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof Rectangle)) return false; + final Rectangle rectangle = (Rectangle) other; + return points().equals(rectangle.points()); + } + + @Override + public int hashCode() { + return Objects.hash(points); + } +} diff --git a/src/main/java/coordinate/domain/StraightLine.java b/src/main/java/coordinate/domain/StraightLine.java new file mode 100644 index 00000000..11cdd19f --- /dev/null +++ b/src/main/java/coordinate/domain/StraightLine.java @@ -0,0 +1,60 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +public class StraightLine implements Line { + + private static final int NUMBER_OF_LINE_POINTS = 2; + + private static final String INVALID_NUMBER_OF_STRAIGHT_LINE_POINTS_EXCEPTION_MESSAGE = "선의 점의 개수는 2개여야 합니다"; + private static final String DUPLICATE_LINE_POINTS_EXCEPTION_MESSAGE = "점 사이에 중복이 존재합니다"; + + private final List points; + + public StraightLine(List points) { + validateNumberOf(points); + validateDuplicateOf(points); + this.points = points; + } + + private void validateNumberOf(final List points) { + if (points.size() != NUMBER_OF_LINE_POINTS) { + throw new IllegalArgumentException(INVALID_NUMBER_OF_STRAIGHT_LINE_POINTS_EXCEPTION_MESSAGE); + } + } + + private void validateDuplicateOf(final List points) { + Set distinctPoints = new HashSet<>(points); + + if (distinctPoints.size() < points.size()) { + throw new IllegalArgumentException(DUPLICATE_LINE_POINTS_EXCEPTION_MESSAGE); + } + } + + private List points() { + return points; + } + + @Override + public double length() { + return points.get(0).distance(points.get(1)); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof StraightLine)) return false; + final StraightLine that = (StraightLine) other; + return points().equals(that.points()); + } + + @Override + public int hashCode() { + return Objects.hash(points); + } +} diff --git a/src/main/java/coordinate/domain/Triangle.java b/src/main/java/coordinate/domain/Triangle.java new file mode 100644 index 00000000..7c921802 --- /dev/null +++ b/src/main/java/coordinate/domain/Triangle.java @@ -0,0 +1,111 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static java.util.Arrays.asList; + +public class Triangle implements Figure { + + private static final String INVALID_NUMBER_OF_TRIANGLE_POINTS_MESSAGE = "삼각형의 점은 3개 여야 합니다"; + private static final int NUMBER_OF_TRIANGLE_POINTS = 3; + private static final String INVALID_TRIANGLE_POINTS_EXCEPTION_MESSAGE = "삼각형이 아닙니다"; + private static final String DUPLICATED_POINTS_EXCEPTION_MESSAGE = "점 사이에 중복이 존재합니다"; + + private final List points; + + public Triangle(final List points) { + validateNumberOf(points); + validateDuplicateOf(points); + validateIsTriangle(points); + this.points = points; + } + + private void validateNumberOf(final List points) { + if (points.size() != NUMBER_OF_TRIANGLE_POINTS) { + throw new IllegalArgumentException(INVALID_NUMBER_OF_TRIANGLE_POINTS_MESSAGE); + } + } + + private void validateDuplicateOf(final List points) { + HashSet distinctPoints = new HashSet<>(points); + + if (distinctPoints.size() < points.size()) { + throw new IllegalArgumentException(DUPLICATED_POINTS_EXCEPTION_MESSAGE); + } + } + + private void validateIsTriangle(final List points) { + List sidesSortedByLengthDesc = getSidesOrderByLengthDescFrom(points); + + if (isLongestSideLongerThanSumOfOtherSides(sidesSortedByLengthDesc)) { + throw new IllegalArgumentException(INVALID_TRIANGLE_POINTS_EXCEPTION_MESSAGE); + } + } + + private List getSidesOrderByLengthDescFrom(final List points) { + return IntStream.range(0, points.size()) + .mapToObj(index -> asList(points.get(index), points.get((index + 1) % NUMBER_OF_TRIANGLE_POINTS))) + .map(StraightLine::new) + .sorted((line, other) -> (int) (other.length() - line.length())) + .collect(Collectors.toUnmodifiableList()); + } + + private boolean isLongestSideLongerThanSumOfOtherSides(final List sidesSortedByDesc) { + return sidesSortedByDesc.get(0).length() >= + sidesSortedByDesc.get(1).length() + sidesSortedByDesc.get(2).length(); + } + + private List points() { + return points; + } + + @Override + public String figureType() { + return "삼각형"; + } + + @Override + public double area() { + List sidesOrderByDesc = getSidesOrderByLengthDescFrom(points); + return heronsFormula(sidesOrderByDesc); + } + + private double heronsFormula(final List sidesOrderByDesc) { + double halfPerimeter = calculateHalfPerimeter(sidesOrderByDesc); + double areaSquared = calculateSquareOfArea(sidesOrderByDesc, halfPerimeter); + return Math.sqrt(areaSquared); + } + + private double calculateHalfPerimeter(final List sidesOrderByDesc) { + double sumOfSides = sidesOrderByDesc.stream() + .mapToDouble(Line::length) + .sum(); + + return sumOfSides / 2; + } + + private double calculateSquareOfArea(final List sidesOrderByDesc, final double halfPerimeter) { + return sidesOrderByDesc.stream() + .mapToDouble(Line::length) + .reduce(halfPerimeter, (total, side) -> total * (halfPerimeter - side)); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Triangle)) return false; + final Triangle triangle = (Triangle) o; + return points().equals(triangle.points()); + } + + @Override + public int hashCode() { + return Objects.hash(points); + } +} diff --git a/src/main/java/coordinate/domain/vo/Coordinate.java b/src/main/java/coordinate/domain/vo/Coordinate.java new file mode 100644 index 00000000..20e0625b --- /dev/null +++ b/src/main/java/coordinate/domain/vo/Coordinate.java @@ -0,0 +1,44 @@ +package coordinate.domain.vo; + +import java.util.Objects; + +public class Coordinate { + + private static final int MIN_COORDINATE_VALUE = 0; + private static final int MAX_COORDINATE_VALUE = 24; + private static final String INVALID_COORDINATE_VALUE_MESSAGE = "좌표 값은 0이상 24이하여야 합니다"; + + private final int value; + + public Coordinate(final int value) { + validateOutOfRange(value); + this.value = value; + } + + private void validateOutOfRange(final int value) { + if (value < MIN_COORDINATE_VALUE || value > MAX_COORDINATE_VALUE) { + throw new IllegalArgumentException(INVALID_COORDINATE_VALUE_MESSAGE); + } + } + + public int distance(final Coordinate other) { + return Math.abs(this.value() - other.value()); + } + + private int value() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof Coordinate)) return false; + final Coordinate that = (Coordinate) o; + return value() == that.value(); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } +} diff --git a/src/main/java/coordinate/domain/vo/Point.java b/src/main/java/coordinate/domain/vo/Point.java new file mode 100644 index 00000000..393faa58 --- /dev/null +++ b/src/main/java/coordinate/domain/vo/Point.java @@ -0,0 +1,56 @@ +package coordinate.domain.vo; + +import java.util.Objects; + +public class Point { + + private static final int EXPONENT = 2; + + private final Coordinate x; + private final Coordinate y; + + public Point(final Coordinate x, final Coordinate y) { + this.x = x; + this.y = y; + } + + public Point(final int x, final int y) { + this(new Coordinate(x), new Coordinate(y)); + } + + public double distance(final Point otherPoint) { + int xDistance = x().distance(otherPoint.x()); + int yDistance = y().distance(otherPoint.y()); + + return Math.sqrt(Math.pow(xDistance, EXPONENT) + Math.pow(yDistance, EXPONENT)); + } + + public Coordinate x() { + return x; + } + + public Coordinate y() { + return y; + } + + public boolean hasSameXCoordinate(final Point otherPoint) { + return x().equals(otherPoint.x()); + } + + public boolean hasSameYCoordinate(final Point otherPoint) { + return y().equals(otherPoint.y()); + } + + @Override + public boolean equals(final Object other) { + if (this == other) return true; + if (!(other instanceof Point)) return false; + final Point point = (Point) other; + return x().equals(point.x()) && y().equals(point.y()); + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } +} diff --git a/src/main/java/coordinate/view/InputConsoleView.java b/src/main/java/coordinate/view/InputConsoleView.java new file mode 100644 index 00000000..8d3de07f --- /dev/null +++ b/src/main/java/coordinate/view/InputConsoleView.java @@ -0,0 +1,61 @@ +package coordinate.view; + +import coordinate.domain.vo.Point; + +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Pattern; + +import static java.lang.System.out; +import static java.util.stream.Collectors.toUnmodifiableList; + +public class InputConsoleView implements InputView { + + private static final int X_COORDINATE_INDEX = 0; + private static final int Y_COORDINATE_INDEX = 1; + + private static final String INPUT_MESSAGE = "좌표를 입력하세요."; + private static final String INVALID_INPUT_EXCEPTION_MESSAGE = "입력 값이 유효하지 않습니다"; + + private static final String POINTS_INPUT_DELIMITER = "-"; + private static final String COORDINATE_DELIMITER = ",[\\s]?"; + + private static final String INPUT_REGEX = "^(\\([0-9]{1,2},[\\s]?[0-9]{1,2}\\))(-\\([0-9]{1,2},[\\s]?[0-9]{1,2}\\))+$"; + private static final Pattern INPUT_PATTERN = Pattern.compile(INPUT_REGEX); + + private final Scanner scanner; + + public InputConsoleView(final Scanner scanner) { + this.scanner = scanner; + } + + @Override + public List input() { + out.println(INPUT_MESSAGE); + String input = scanner.nextLine(); + return parsePointsFrom(input); + } + + private List parsePointsFrom(final String input) { + validateOf(input); + String[] splattedPoints = input.split(POINTS_INPUT_DELIMITER); + return Arrays.stream(splattedPoints) + .map(this::parsePointFrom) + .collect(toUnmodifiableList()); + } + + private void validateOf(final String input) { + if (!INPUT_PATTERN.matcher(input).matches()) { + throw new IllegalArgumentException(INVALID_INPUT_EXCEPTION_MESSAGE); + } + } + + private Point parsePointFrom(final String token) { + String parenthesesRemovedToken = token.substring(1, token.length() - 1); + String[] splattedCoordinate = parenthesesRemovedToken.split(COORDINATE_DELIMITER); + int x = Integer.parseInt(splattedCoordinate[X_COORDINATE_INDEX]); + int y = Integer.parseInt(splattedCoordinate[Y_COORDINATE_INDEX]); + return new Point(x, y); + } +} diff --git a/src/main/java/coordinate/view/InputView.java b/src/main/java/coordinate/view/InputView.java new file mode 100644 index 00000000..230aadee --- /dev/null +++ b/src/main/java/coordinate/view/InputView.java @@ -0,0 +1,9 @@ +package coordinate.view; + +import coordinate.domain.vo.Point; + +import java.util.List; + +public interface InputView { + List input(); +} diff --git a/src/main/java/coordinate/view/OutputConsoleView.java b/src/main/java/coordinate/view/OutputConsoleView.java new file mode 100644 index 00000000..5091b59a --- /dev/null +++ b/src/main/java/coordinate/view/OutputConsoleView.java @@ -0,0 +1,22 @@ +package coordinate.view; + +import coordinate.domain.Figure; +import coordinate.domain.Line; + +import static java.lang.System.out; + +public class OutputConsoleView implements OutputView { + + private static final String LINE_PRINT_FORMAT = "두 점 사이 거리는 %f%n"; + private static final String FIGURE_PRINT_FORMAT = "%s의 넓이는 %f%n"; + + @Override + public void printLine(final Line line) { + out.printf(LINE_PRINT_FORMAT, line.length()); + } + + @Override + public void printFigure(final Figure figure) { + out.printf(FIGURE_PRINT_FORMAT, figure.figureType(), figure.area()); + } +} diff --git a/src/main/java/coordinate/view/OutputView.java b/src/main/java/coordinate/view/OutputView.java new file mode 100644 index 00000000..4e2ce56f --- /dev/null +++ b/src/main/java/coordinate/view/OutputView.java @@ -0,0 +1,11 @@ +package coordinate.view; + +import coordinate.domain.Figure; +import coordinate.domain.Line; + +public interface OutputView { + + void printLine(final Line line); + + void printFigure(final Figure figure); +} diff --git a/src/main/java/rentcar/domain/RentCompany.java b/src/main/java/rentcar/domain/RentCompany.java new file mode 100644 index 00000000..6b3be59d --- /dev/null +++ b/src/main/java/rentcar/domain/RentCompany.java @@ -0,0 +1,41 @@ +package rentcar.domain; + +import rentcar.domain.car.Car; + +import java.util.ArrayList; +import java.util.List; + +public class RentCompany { + + private static final String NEWLINE = System.getProperty("line.separator"); + private static final String DELIMITER = " : "; + private static final String LITER = "리터"; + + List cars; + + private RentCompany(final List cars) { + this.cars = cars; + } + + public static RentCompany create() { + return new RentCompany(new ArrayList<>()); + } + + public void addCar(final Car car) { + cars.add(car); + } + + public String generateReport() { + StringBuilder report = new StringBuilder(); + + for (Car car : cars) { + report.append(car.getName()); + report.append(DELIMITER); + report.append((int) car.getChargeQuantity()); + report.append(LITER); + report.append(NEWLINE); + } + + return report.toString(); + } +} diff --git a/src/main/java/rentcar/domain/car/Avante.java b/src/main/java/rentcar/domain/car/Avante.java new file mode 100644 index 00000000..32c04380 --- /dev/null +++ b/src/main/java/rentcar/domain/car/Avante.java @@ -0,0 +1,40 @@ +package rentcar.domain.car; + +import rentcar.domain.car.vo.DistancePerLiter; +import rentcar.domain.car.vo.TripDistance; + +public class Avante extends Car{ + + private static final double DISTANCE_PER_LITER = 15; + + private final TripDistance tripDistance; + private final DistancePerLiter distancePerLiter; + + public Avante(final TripDistance tripDistance, final DistancePerLiter distancePerLiter) { + this.tripDistance = tripDistance; + this.distancePerLiter = distancePerLiter; + } + + public Avante(final double tripDistance, final double distancePerLiter) { + this(new TripDistance(tripDistance), new DistancePerLiter(distancePerLiter)); + } + + public Avante(final double tripDistance) { + this(tripDistance, DISTANCE_PER_LITER); + } + + @Override + public double getDistancePerLiter() { + return distancePerLiter.getValue(); + } + + @Override + public double getTripDistance() { + return tripDistance.getValue(); + } + + @Override + public String getName() { + return "Avante"; + } +} diff --git a/src/main/java/rentcar/domain/car/Car.java b/src/main/java/rentcar/domain/car/Car.java new file mode 100644 index 00000000..c1226f6f --- /dev/null +++ b/src/main/java/rentcar/domain/car/Car.java @@ -0,0 +1,14 @@ +package rentcar.domain.car; + +public abstract class Car { + + protected abstract double getDistancePerLiter(); + + protected abstract double getTripDistance(); + + public abstract String getName(); + + public double getChargeQuantity() { + return getTripDistance() / getDistancePerLiter(); + } +} diff --git a/src/main/java/rentcar/domain/car/K5.java b/src/main/java/rentcar/domain/car/K5.java new file mode 100644 index 00000000..bdb339a2 --- /dev/null +++ b/src/main/java/rentcar/domain/car/K5.java @@ -0,0 +1,40 @@ +package rentcar.domain.car; + +import rentcar.domain.car.vo.DistancePerLiter; +import rentcar.domain.car.vo.TripDistance; + +public class K5 extends Car { + + private static final double DISTANCE_PER_LITER = 13; + + private final TripDistance tripDistance; + private final DistancePerLiter distancePerLiter; + + public K5(final TripDistance tripDistance, final DistancePerLiter distancePerLiter) { + this.tripDistance = tripDistance; + this.distancePerLiter = distancePerLiter; + } + + public K5(final double tripDistance, final double distancePerLiter) { + this(new TripDistance(tripDistance), new DistancePerLiter(distancePerLiter)); + } + + public K5(final double tripDistance) { + this(tripDistance, DISTANCE_PER_LITER); + } + + @Override + protected double getDistancePerLiter() { + return distancePerLiter.getValue(); + } + + @Override + protected double getTripDistance() { + return tripDistance.getValue(); + } + + @Override + public String getName() { + return "K5"; + } +} diff --git a/src/main/java/rentcar/domain/car/Sonata.java b/src/main/java/rentcar/domain/car/Sonata.java new file mode 100644 index 00000000..91e8ec26 --- /dev/null +++ b/src/main/java/rentcar/domain/car/Sonata.java @@ -0,0 +1,40 @@ +package rentcar.domain.car; + +import rentcar.domain.car.vo.DistancePerLiter; +import rentcar.domain.car.vo.TripDistance; + +public class Sonata extends Car { + + private static final int DISTANCE_PER_LITER = 10; + + private final TripDistance tripDistance; + private final DistancePerLiter distancePerLiter; + + public Sonata(final TripDistance tripDistance, final DistancePerLiter distancePerLiter) { + this.tripDistance = tripDistance; + this.distancePerLiter = distancePerLiter; + } + + public Sonata(final double tripDistance, final double distancePerLiter) { + this(new TripDistance(tripDistance), new DistancePerLiter(distancePerLiter)); + } + + public Sonata(final double tripDistance) { + this(tripDistance, DISTANCE_PER_LITER); + } + + @Override + protected double getDistancePerLiter() { + return distancePerLiter.getValue(); + } + + @Override + protected double getTripDistance() { + return tripDistance.getValue(); + } + + @Override + public String getName() { + return "Sonata"; + } +} diff --git a/src/main/java/rentcar/domain/car/vo/DistancePerLiter.java b/src/main/java/rentcar/domain/car/vo/DistancePerLiter.java new file mode 100644 index 00000000..de80eea0 --- /dev/null +++ b/src/main/java/rentcar/domain/car/vo/DistancePerLiter.java @@ -0,0 +1,21 @@ +package rentcar.domain.car.vo; + +public class DistancePerLiter { + + private final double value; + + public DistancePerLiter(final double value) { + validateGreaterThanZero(value); + this.value = value; + } + + private void validateGreaterThanZero(final double value) { + if (value <= 0) { + throw new IllegalArgumentException("0 초과의 수 여야 합니다"); + } + } + + public double getValue() { + return value; + } +} diff --git a/src/main/java/rentcar/domain/car/vo/TripDistance.java b/src/main/java/rentcar/domain/car/vo/TripDistance.java new file mode 100644 index 00000000..056450e6 --- /dev/null +++ b/src/main/java/rentcar/domain/car/vo/TripDistance.java @@ -0,0 +1,21 @@ +package rentcar.domain.car.vo; + +public class TripDistance { + + private final double value; + + public TripDistance(final double value) { + validateZeroOrMore(value); + this.value = value; + } + + private void validateZeroOrMore(final double value) { + if (value < 0) { + throw new IllegalArgumentException("0 이상의 수어야 합니다"); + } + } + + public double getValue() { + return value; + } +} diff --git a/src/test/java/coordinate/domain/FigureFactoryTest.java b/src/test/java/coordinate/domain/FigureFactoryTest.java new file mode 100644 index 00000000..f3eb9be6 --- /dev/null +++ b/src/test/java/coordinate/domain/FigureFactoryTest.java @@ -0,0 +1,40 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +class FigureFactoryTest { + + @Test + @DisplayName("점 3개를 입력받아 삼각형을 생성한다") + void create_rectangle() { + //given + List points = asList(new Point(1, 1), new Point(2, 2), new Point(3, 1)); + + //when + Figure actual = FigureFactory.create(points); + + //then + assertThat(actual).isEqualTo(new Triangle(points)); + } + + @Test + @DisplayName("점 4개를 입력받아 사각형을 생성한다") + void create_triangle() { + //given + List points = + asList(new Point(1, 1), new Point(2, 1), new Point(1, 2), new Point(2, 2)); + + //when + Figure actual = FigureFactory.create(points); + + //then + assertThat(actual).isEqualTo(new Rectangle(points)); + } +} diff --git a/src/test/java/coordinate/domain/RectangleTest.java b/src/test/java/coordinate/domain/RectangleTest.java new file mode 100644 index 00000000..612c4ffc --- /dev/null +++ b/src/test/java/coordinate/domain/RectangleTest.java @@ -0,0 +1,98 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.offset; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class RectangleTest { + + @Test + @DisplayName("점 4개를 인자로 받아 점을 생성한다") + void create() { + //given + List points = + asList(new Point(0, 0), new Point(1, 1), new Point(0, 1), new Point(1, 0)); + + //when + Rectangle rectangle = new Rectangle(points); + + //then + assertThat(rectangle).isEqualTo(new Rectangle(points)); + } + + @Test + @DisplayName("점이 4개가 아니면 예외를 발생시킨다") + void create_throw_exception_with_invalid_number_of_points() { + //given + List points = Collections.singletonList(new Point(1, 1)); + + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Rectangle(points)) + .withMessage("사각형의 점은 4개여야 합니다"); + } + + @ParameterizedTest + @MethodSource("isNotRectanglePoints") + @DisplayName("직사각형을 이루는 점이 아니라면 예외를 발생시킨다") + void create_throw_exception_with_is_not_rectangle_points(final List points) { + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Rectangle(points)) + .withMessage("직사각형이 아닙니다"); + } + + @Test + @DisplayName("") + void create_throw_exception_with_duplicate_points() { + //given + List points = + asList(new Point(0, 0), new Point(0, 0), new Point(1, 1), new Point(1, 1)); + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Rectangle(points)) + .withMessage("점 사이에 중복이 존재합니다"); + } + + static Stream isNotRectanglePoints() { + return Stream.of( + arguments( + asList( + new Point(0, 0), new Point(1, 0), + new Point(2, 0), new Point(3, 0)), + asList( + new Point(0, 0), new Point(0, 1), + new Point(0, 2), new Point(0, 3)), + asList( + new Point(0, 0), new Point(1, 0), + new Point(0, 1), new Point(1, 2)), + asList( + new Point(0, 0), new Point(1, 1), + new Point(2, 2), new Point(3, 3)))); + } + + @Test + @DisplayName("넓이를 반환한다") + void area() { + //given + List points = + asList(new Point(0, 0), new Point(1, 1), new Point(0, 1), new Point(1, 0)); + Rectangle rectangle = new Rectangle(points); + + //when + double area = rectangle.area(); + + //then + assertThat(area).isEqualTo(1, offset(0.001)); + } +} diff --git a/src/test/java/coordinate/domain/StraightLineTest.java b/src/test/java/coordinate/domain/StraightLineTest.java new file mode 100644 index 00000000..ec8852f3 --- /dev/null +++ b/src/test/java/coordinate/domain/StraightLineTest.java @@ -0,0 +1,65 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.offset; + +class StraightLineTest { + + @Test + @DisplayName("선을 생성한다") + void create() { + //given + List points = asList(new Point(0, 0), new Point(1, 1)); + + //when + Line line = new StraightLine(points); + + //then + assertThat(line).isEqualTo(new StraightLine(points)); + + } + + @Test + @DisplayName("점의 개수가 2개 이상이면 예외를 발생시킨다") + void create_throw_exception_with_invalid_number_of_points() { + //given + List points = Collections.singletonList(new Point(0, 0)); + + //then + assertThatIllegalArgumentException().isThrownBy(() -> new StraightLine(points)) + .withMessage("선의 점의 개수는 2개여야 합니다"); + } + + @Test + @DisplayName("점 사이에 중복이 존재하면 예외를 발생시킨다") + void create_throw_exception_with_duplicate_points() { + //given + List points = asList(new Point(0, 0), new Point(0, 0)); + + //then + assertThatIllegalArgumentException().isThrownBy(() -> new StraightLine(points)) + .withMessage("점 사이에 중복이 존재합니다"); + } + + @Test + @DisplayName("선의 길이를 반환한다") + void length() { + //given + List points = asList(new Point(0, 0), new Point(1, 1)); + Line line = new StraightLine(points); + + //when + double length = line.length(); + + assertThat(length).isEqualTo(1.414, offset(0.001)); + } +} diff --git a/src/test/java/coordinate/domain/TriangleTest.java b/src/test/java/coordinate/domain/TriangleTest.java new file mode 100644 index 00000000..93a915f9 --- /dev/null +++ b/src/test/java/coordinate/domain/TriangleTest.java @@ -0,0 +1,61 @@ +package coordinate.domain; + +import coordinate.domain.vo.Point; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class TriangleTest { + + @Test + @DisplayName("삼각형을 생성한다") + void create() { + //given + List points = Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(0, 3)); + + //when + Triangle triangle = new Triangle(points); + + //then + assertThat(triangle).isEqualTo(new Triangle(points)); + } + + @Test + @DisplayName("점이 3개가 아니라면 예외를 발생시킨다") + void create_throw_exception_with_invalid_number_of_points() { + //given + List points = Collections.singletonList(new Point(0, 0)); + + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Triangle(points)) + .withMessage("삼각형의 점은 3개 여야 합니다"); + } + + @ParameterizedTest + @MethodSource("isNotTrianglePoints") + @DisplayName("점 3개가 삼각형이 아닐 경우 예외를 발생시킨다") + void create_throw_exception_with_is_not_triangle(final List points) { + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Triangle(points)) + .withMessage("삼각형이 아닙니다"); + } + + static Stream isNotTrianglePoints() { + return Stream.of( + arguments( + Arrays.asList(new Point(0, 0), new Point(0, 1), new Point(0, 2)), + Arrays.asList(new Point(0, 0), new Point(1, 0), new Point(2, 0)), + Arrays.asList(new Point(0, 0), new Point(1, 1), new Point(2, 2)))); + } +} diff --git a/src/test/java/coordinate/domain/vo/CoordinateTest.java b/src/test/java/coordinate/domain/vo/CoordinateTest.java new file mode 100644 index 00000000..e08057a1 --- /dev/null +++ b/src/test/java/coordinate/domain/vo/CoordinateTest.java @@ -0,0 +1,51 @@ +package coordinate.domain.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +class CoordinateTest { + + @Test + @DisplayName("좌표 값을 생성한다") + void create() { + //given + int value = 0; + + //when + Coordinate coordinate = new Coordinate(value); + + //then + assertThat(coordinate).isEqualTo(new Coordinate(value)); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 25}) + @DisplayName("0미만 24초과의 값은 예외를 발생시킨다") + void create_throw_exception_with_out_of_range(final int outOfRangeValue) { + //then + assertThatIllegalArgumentException().isThrownBy(() -> new Coordinate(outOfRangeValue)) + .withMessageContaining("좌표 값은 0이상 24이하여야 합니다"); + } + + @Test + @DisplayName("다른 좌표값과 거리를 반환한다") + void distance() { + //given + int value = 1; + Coordinate coordinate = new Coordinate(value); + + int otherValue = 2; + Coordinate otherCoordinate = new Coordinate(otherValue); + + //when + int distance = coordinate.distance(otherCoordinate); + + //then + assertThat(distance).isEqualTo(1); + } +} diff --git a/src/test/java/coordinate/domain/vo/PointTest.java b/src/test/java/coordinate/domain/vo/PointTest.java new file mode 100644 index 00000000..4979253d --- /dev/null +++ b/src/test/java/coordinate/domain/vo/PointTest.java @@ -0,0 +1,119 @@ +package coordinate.domain.vo; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; + +class PointTest { + + @Test + @DisplayName("X 좌표와 Y 좌표를 입력받아 점을 생성한다") + void create_with_x_and_y() { + //given + int xValue = 0; + int yValue = 0; + + Coordinate x = new Coordinate(xValue); + Coordinate y = new Coordinate(yValue); + + //when + Point point = new Point(x, y); + + //then + assertThat(point).isEqualTo(new Point(x, y)); + } + + @Test + @DisplayName("X 좌표값과 Y 좌표값을 입력받아 점을 생성한다") + void create_with_x_value_and_y_value() { + //given + int xValue = 0; + int yValue = 0; + + //when + Point point = new Point(xValue, yValue); + + //then + assertThat(point).isEqualTo(new Point(xValue, yValue)); + } + + @Test + @DisplayName("두 점 사이의 거리를 반환한다") + void distance() { + //given + Point point = new Point(0, 0); + Point otherPoint = new Point(1, 1); + + //when + double distance = point.distance(otherPoint); + + //then + assertThat(distance).isEqualTo(1.414, offset(0.001)); + } + + @Test + @DisplayName("x 좌표를 반환한다") + void x() { + //given + int xValue = 1; + int yValue = 1; + Point point = new Point(xValue, yValue); + + //when + Coordinate x = point.x(); + + //then + assertThat(x).isEqualTo(new Coordinate(xValue)); + } + + @Test + @DisplayName("y 좌표를 반환한다") + void y() { + //given + int xValue = 1; + int yValue = 1; + Point point = new Point(xValue, yValue); + + //when + Coordinate y = point.y(); + + //then + assertThat(y).isEqualTo(new Coordinate(yValue)); + } + + @ParameterizedTest + @CsvSource(value = {"1, 1, true", "1, 2, false"}) + @DisplayName("다른 점을 입력받아 같은 같은 X 좌표의 일치 여부를 반환한다") + void hasSameXCoordinate(final int xValue, final int otherXValue, final boolean expected) { + //given + int yValue = 1; + Point point = new Point(xValue, yValue); + Point otherPoint = new Point(otherXValue, yValue); + + //when + boolean actual = point.hasSameXCoordinate(otherPoint); + + //then + assertThat(actual).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"1, 1, true", "1, 2, false"}) + @DisplayName("다른 점을 입력받아 같은 같은 X 좌표의 일치 여부를 반환한다") + void hasSameYCoordinate(final int yValue, final int otherYValue, final boolean expected) { + //given + int xValue = 1; + Point point = new Point(xValue, yValue); + Point otherPoint = new Point(xValue, otherYValue); + + //when + boolean actual = point.hasSameYCoordinate(otherPoint); + + //then + assertThat(actual).isEqualTo(expected); + } +} diff --git a/src/test/java/rentcar/domain/RentCompanyTest.java b/src/test/java/rentcar/domain/RentCompanyTest.java new file mode 100644 index 00000000..32a030ed --- /dev/null +++ b/src/test/java/rentcar/domain/RentCompanyTest.java @@ -0,0 +1,33 @@ +package rentcar.domain; + +import org.junit.jupiter.api.Test; +import rentcar.domain.car.Avante; +import rentcar.domain.car.Car; +import rentcar.domain.car.K5; +import rentcar.domain.car.Sonata; + +import static org.assertj.core.api.Assertions.assertThat; + +class RentCompanyTest { + private static final String NEWLINE = System.getProperty("line.separator"); + + @Test + void report() { + RentCompany company = RentCompany.create(); + Car car = new Sonata(150); + company.addCar(new Sonata(150)); + company.addCar(new K5(260)); + company.addCar(new Sonata(120)); + company.addCar(new Avante(300)); + company.addCar(new K5(390)); + + String report = company.generateReport(); + assertThat(report).isEqualTo( + "Sonata : 15리터" + NEWLINE + + "K5 : 20리터" + NEWLINE + + "Sonata : 12리터" + NEWLINE + + "Avante : 20리터" + NEWLINE + + "K5 : 30리터" + NEWLINE + ); + } +}