자바/초록스터디

(9)초록스터디 로또 3,4 단계 최종 구현

문상휘파람 2024. 7. 15. 17:54

 

이번 미션 진행하면서 dto, enum, interface 등 새로운 기술들을 많이 사용하였고 이에 따라 많은 피드백을 받을 수 있었다. 근데 정말 피드백이 너무 많아서 하나 하나 다 설명하기엔 양이 너어어어어~~~~무 많으므로 최종적인 코드를 첨부하고자 한다 !! start!


*LottoContorller

package controller;

import domain.*;
import view.InputView;
import view.LottoRankDto;
import view.OutputView;

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

public class LottoController {

    private static final int RESET_NUMBER = 0;

    private final InputView inputView;
    private final OutputView outputView;

    public LottoController(InputView inputView, OutputView outputView) {
        this.inputView = inputView;
        this.outputView = outputView;
    }

    public void startLotto() {
        int lottoMoney = getLottoMoney();
        Lottos lottos = buyLotto(lottoMoney);
        List<Integer> lastWeekLottoNumber = getLastWeekLottoNumber();
        rankLotto(lottos, lastWeekLottoNumber, lottoMoney);
    }

    private int getLottoMoney() {
        outputView.printGetLottoMoney();
        return inputView.getLottoMoney();
    }

    private Lottos buyLotto(int getLottoMoney) {
        outputView.printPassiveLottoCount();
        int passiveLottoNumberCount = inputView.getPassiveLottoCount();
        outputView.printPassiveLottoNumber();
        List<String> passiveLottoNumbers = new ArrayList<>();
        for (int i = RESET_NUMBER; i < passiveLottoNumberCount; i++) {
            String passiveLottoNumber = inputView.inputLottoNumber();
            passiveLottoNumbers.add(passiveLottoNumber);
        }
        PassiveLottoNumber passiveLottoNumber = new PassiveLottoNumber(passiveLottoNumbers);
        outputView.printLottoCount(getLottoMoney, passiveLottoNumberCount);
        CreateLottoNumber createLottoNumber = new LottoNumberGenerator();
        Lottos lottos = new Lottos(createLottoNumber, passiveLottoNumber.getPassiveLottoNumbers(), getLottoMoney);
        for (Lotto lotto : lottos.getLottos()) {
            List<Integer> lottoNumber = lotto.getLottoNumber().stream().map(LottoNumber::getLottoNumber).toList();
            outputView.printLotto(lottoNumber);
        }
        return lottos;
    }

    private List<Integer> getLastWeekLottoNumber() {
        outputView.LastWeekLottoNumber();
        String inputLastWeekLottoNumber = inputView.inputLottoNumber();
        LottoNumberParser lottoNumberParser = new LottoNumberParser(inputLastWeekLottoNumber);
        int bonusBall = getBonusBall();
        lottoNumberParser.addBonusBall(bonusBall);
        return lottoNumberParser.getRealLottoNumber();
    }

    private int getBonusBall() {
        outputView.inputBonusBall();
        return inputView.inputBonusBall();
    }

    private void rankLotto(Lottos lottos, List<Integer> lastWeekLottoNumber, int getLottoMoney) {
        LottoRankBundle lottoRankBundle = new LottoRankBundle(lottos, lastWeekLottoNumber);
        LottoReturnRate lottoReturnRate = new LottoReturnRate(lottoRankBundle.getLottoRank(), getLottoMoney);
        outputView.printLottoStatistics();
        for(LottoRank lottoRank : lottoRankBundle.getLottoRank()){
            LottoRankDto lottoRankDto = new LottoRankDto(lottoRank.getLottoRank(), lottoRank.getLottoRankNumber());
            outputView.printLottoRank(lottoRankDto.toString());
        }
        outputView.printRateOfReturn(lottoReturnRate.getLottoReturnRate());
    }
}

 

*CreateLottoNumber(interface)

package domain;

import java.util.List;

public interface CreateLottoNumber {

    List<Integer> getRandomLottoNumber();
}

 

*Lotto

package domain;

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

public class Lotto {

    private final List<LottoNumber> lottoNumber;

    public Lotto(CreateLottoNumber createLottoNumber) {
        this.lottoNumber = makeAutoLotto(createLottoNumber);
    }

    public Lotto(List<Integer> inputPassiveLottoNumber) {
        this.lottoNumber = makePassiveLotto(inputPassiveLottoNumber);
    }

    public List<LottoNumber> getLottoNumber() {
        return lottoNumber;
    }

    private List<LottoNumber> makeAutoLotto(CreateLottoNumber createLottoNumber) {
        List<Integer> singleLotto = new ArrayList<>(createLottoNumber.getRandomLottoNumber());
        sortLottoNumber(singleLotto);
        List<LottoNumber> lottoNumbers = new ArrayList<>();
        for (int lottoNumberElement : singleLotto) {
            LottoNumber lottoNumber = new LottoNumber(lottoNumberElement);
            lottoNumbers.add(lottoNumber);
        }
        return lottoNumbers;
    }

    private List<LottoNumber> makePassiveLotto(List<Integer> inputPassiveLottoNumber) {
        List<Integer> passiveLottoNumber = new ArrayList<>(inputPassiveLottoNumber);
        List<LottoNumber> lottoNumbers = new ArrayList<>();
        for (int lottoNumberElement : passiveLottoNumber) {
            LottoNumber lottoNumber = new LottoNumber(lottoNumberElement);
            lottoNumbers.add(lottoNumber);
        }
        return lottoNumbers;
    }

    private void sortLottoNumber(List<Integer> lottoNumber) {
        Collections.sort(lottoNumber);
    }
}

 

 

*Lottos

package domain;

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

public class Lottos {

    private static final int INITIAL_NUMBER = 0;
    private static final int LOTTO_COUNT_NUMBER = 1000;

    private final List<Lotto> lottos;

    public Lottos(CreateLottoNumber createLottoNumber, List<LottoNumberParser> inputPassiveLottoNumber, int lottoMoney) {
        this.lottos = makeLottos(createLottoNumber, inputPassiveLottoNumber, lottoMoney);
    }

    public List<Lotto> getLottos() {
        return lottos;
    }

    private List<Lotto> makeLottos(CreateLottoNumber createLottoNumber, List<LottoNumberParser> inputPassiveLottoNumbers, int lottoMoney) {
        List<Lotto> lottosBundle = new ArrayList<>();
        makePassiveLotto(inputPassiveLottoNumbers, lottosBundle);
        makeAutoLotto(createLottoNumber, inputPassiveLottoNumbers, lottoMoney, lottosBundle);
        return lottosBundle;
    }

    private void makeAutoLotto(CreateLottoNumber createLottoNumber, List<LottoNumberParser> inputPassiveLottoNumbers, int lottoMoney, List<Lotto> lottosBundle) {
        for (int i = INITIAL_NUMBER; i < (lottoMoney / LOTTO_COUNT_NUMBER) - inputPassiveLottoNumbers.size(); i++) {
            Lotto lotto = new Lotto(createLottoNumber);
            lottosBundle.add(lotto);
        }
    }

    private void makePassiveLotto(List<LottoNumberParser> inputPassiveLottoNumbers, List<Lotto> lottosBundle) {
        for (LottoNumberParser passiveLottoNumber : inputPassiveLottoNumbers) {
            Lotto lotto = new Lotto(passiveLottoNumber.getRealLottoNumber());
            lottosBundle.add(lotto);
        }
    }
}

 

*LottoNumber

package domain;

public class LottoNumber {

    private static final int FIRST_LOTTO_NUMBER = 1;
    private static final int LAST_LOTTO_NUMBER = 45;
    private static final String EXCEPTION_LOTTO_RANGE = "로또 범위 아님";

    private final int lottoNumber;

    public LottoNumber(int lottoNumber) {
        validateLottoNumberRange(lottoNumber);
        this.lottoNumber = lottoNumber;
    }

    public int getLottoNumber() {
        return lottoNumber;
    }

    private void validateLottoNumberRange(int realLottoNumber) {
        if (realLottoNumber < FIRST_LOTTO_NUMBER || realLottoNumber > LAST_LOTTO_NUMBER) {
            throw new RuntimeException(EXCEPTION_LOTTO_RANGE);
        }
    }
}

로또 넘버 전체를 원시값 포장해줌!!

 

*PassiveLottoNumber(수동 로또 넘버)

package domain;

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

public class PassiveLottoNumber {

    private final List<LottoNumberParser> passiveLottoNumbers;

    public PassiveLottoNumber(List<String> passiveLottoNumber) {
        this.passiveLottoNumbers = makePassiveLottoNumbers(passiveLottoNumber);
    }

    public List<LottoNumberParser> getPassiveLottoNumbers() {
        return passiveLottoNumbers;
    }

    private List<LottoNumberParser> makePassiveLottoNumbers(List<String> passiveLottoNumbers) {
        List<LottoNumberParser> lottoNumbers = new ArrayList<>();
        for (String passiveLottoNumber : passiveLottoNumbers) {
            LottoNumberParser lottoNumberParser = new LottoNumberParser(passiveLottoNumber);
            lottoNumbers.add(lottoNumberParser);
        }
        return lottoNumbers;
    }
}

 

*LottoNumberGenerator(인터페이스 상속받은 클래스)

package domain;

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

public class LottoNumberGenerator implements CreateLottoNumber {

    private static final int INITIAL_NUMBER = 0;
    private static final int FIRST_LOTTO_NUMBER = 1;
    private static final int LAST_LOTTO_NUMBER = 45;
    private static final int LOTTO_NUMBER_LENGTH_BOUNDARY = 6;
    private static final Random randomNumberGenerator = new Random();

    @Override
    public List<Integer> getRandomLottoNumber() {
        List<Integer> getLotto = new ArrayList<>();
        for (int i = INITIAL_NUMBER; i < LOTTO_NUMBER_LENGTH_BOUNDARY; i++) {
            int randomLottoNumber = checkDuplicateNumber(getLotto, randomNumberGenerator.nextInt(FIRST_LOTTO_NUMBER, LAST_LOTTO_NUMBER));
            getLotto.add(randomLottoNumber);
        }
        return getLotto;
    }

    private int generateRandomNumber() {
        return randomNumberGenerator.nextInt(FIRST_LOTTO_NUMBER, LAST_LOTTO_NUMBER);
    }

    private int checkDuplicateNumber(List<Integer> getLotto, int randomLottoNumber) {
        if (getLotto.contains(randomLottoNumber)) {
            return checkDuplicateNumber(getLotto, generateRandomNumber());
        }
        return randomLottoNumber;
    }
}

 

*LottoNumberParser

package domain;

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

public class LottoNumberParser {

    private static final String SPLIT_STRING_DELIMITER = ",";
    private static final String EXCEPTION_NUMBER = "올바른 숫자 입력하세요.";

    private final List<Integer> realLottoNumber;

    public LottoNumberParser(String lottoNumber) {
        this.realLottoNumber = makeRealLottoNumber(lottoNumber);
    }

    public List<Integer> getRealLottoNumber() {
        return realLottoNumber;
    }

    public void addBonusBall(int bonusBall) {
        realLottoNumber.add(bonusBall);
    }

    private List<Integer> makeRealLottoNumber(String lottoNumber) {
        List<String> inputLottoNumber = parseLottoNumber(lottoNumber);
        List<Integer> realLottoNumber = new ArrayList<>();
        for (String lottoNumberElement : inputLottoNumber) {
            validateLottoNumber(lottoNumberElement);
            int realLottoNumberElement = Integer.parseInt(lottoNumberElement);
            realLottoNumber.add(realLottoNumberElement);
        }
        return realLottoNumber;
    }

    private List<String> parseLottoNumber(String lottoNumber) {
        String[] parseLottoNumber = lottoNumber.split(SPLIT_STRING_DELIMITER);
        return List.of(parseLottoNumber);
    }

    private void validateLottoNumber(String lottoNumber) {
        try {
            Integer.parseInt(lottoNumber);
        } catch (NumberFormatException exception) {
            throw new RuntimeException(EXCEPTION_NUMBER);
        }
    }
}

 

*LottoPrice(enum)

package domain;

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

public enum LottoPrice {

    THREE("3", 5000),
    FOUR("4", 50000),
    FIVE("5", 1500000),
    BONUS("5BONUS", 30000000),
    SIX("6", 2000000000);

    private static final int MAKE_FIRST_INDEX = 3;
    private static final String LAST_INDEX_NUMBER = "6";

    private final String sameLottoNumber;
    private final int lottoPrice;

    LottoPrice(String sameLottoNumber, int lottoPrice) {
        this.sameLottoNumber = sameLottoNumber;
        this.lottoPrice = lottoPrice;
    }

    public static List<Integer> getLottoPriceBundle() {
        List<Integer> lottoPrices = new ArrayList<>();
        for (LottoPrice lottoPrice : LottoPrice.values()) {
            lottoPrices.add(lottoPrice.getLottoPrice());
        }
        return lottoPrices;
    }

    public static int getLottoPrice(String sameLottoNumber) {
        if (Objects.equals(sameLottoNumber, BONUS.getSameLottoNumber())) {
            return BONUS.getLottoPrice();
        }
        if (Objects.equals(sameLottoNumber, LAST_INDEX_NUMBER)) {
            return SIX.getLottoPrice();
        }
        LottoPrice[] lottoPrices = LottoPrice.values();
        return lottoPrices[Integer.parseInt(sameLottoNumber) - MAKE_FIRST_INDEX].getLottoPrice();
    }

    public static List<String> getSameLottoNumberBundle() {
        List<String> sameLottoNumbers = new ArrayList<>();
        for (LottoPrice lottoPrice : LottoPrice.values()) {
            sameLottoNumbers.add(lottoPrice.getSameLottoNumber());
        }
        return sameLottoNumbers;
    }

    public static String getBonusSameLottoNumber() {
        return BONUS.getSameLottoNumber();
    }

    private String getSameLottoNumber() {
        return sameLottoNumber;
    }

    private int getLottoPrice() {
        return lottoPrice;
    }
}

 

*LottoRank

package domain;

public class LottoRank {

    private final String lottoRank;
    private final int lottoRankNumber;

    public LottoRank(String lottoRank, int lottoRankNumber) {
        this.lottoRank = lottoRank;
        this.lottoRankNumber = lottoRankNumber;
    }

    public String getLottoRank() {
        return lottoRank;
    }

    public int getLottoRankNumber() {
        return lottoRankNumber;
    }
}

 

*LottoRankBundle

package domain;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.HashMap;


public class LottoRankBundle {

    private static final int RESET_NUMBER = 0;
    private static final int INITIAL_NUMBER = 1;
    private static final int BONUS_COUNT = 5;

    private final List<LottoRank> lottoRank;

    public LottoRankBundle(Lottos lottos, List<Integer> lastWeekLottoNumber) {
        this.lottoRank = makeLottoRankBundle(lottos, lastWeekLottoNumber);
    }

    public List<LottoRank> getLottoRank() {
        return lottoRank;
    }

    private List<LottoRank> makeLottoRankBundle(Lottos lottos, List<Integer> lastWeekLottoNumber) {
        List<LottoRank> lottoRanks = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : makeLottoRank(lottos, lastWeekLottoNumber).entrySet()) {
            lottoRanks.add(new LottoRank(entry.getKey(), entry.getValue()));
        }
        return lottoRanks;
    }

    private Map<String, Integer> makeLottoRank(Lottos lottos, List<Integer> lastWeekLottoNumber) {
        Map<String, Integer> rankLotto = checkCorrespondingLottosNumber(lottos, lastWeekLottoNumber);
        List<String> sameLottoNumbers = LottoPrice.getSameLottoNumberBundle();
        Map<String, Integer> sortedRankLotto = new HashMap<>();
        for (String sameLottoNumber : sameLottoNumbers) {
            rankLotto.put(sameLottoNumber, rankLotto.getOrDefault(sameLottoNumber, RESET_NUMBER) + RESET_NUMBER);
            sortedRankLotto.put(sameLottoNumber, rankLotto.getOrDefault(sameLottoNumber, rankLotto.get(sameLottoNumber)));
        }
        return new TreeMap<>(sortedRankLotto);
    }

    private Map<String, Integer> checkCorrespondingLottosNumber(Lottos lottos, List<Integer> lastWeekLottoNumber) {
        Map<String, Integer> rankLotto = new HashMap<>();
        for (Lotto lotto : lottos.getLottos()) {
            List<Integer> lottoNumber = lotto.getLottoNumber().stream().map(LottoNumber::getLottoNumber).toList();
            String sameLottoNumber = checkCorrespondingLottoNumber(lottoNumber, lastWeekLottoNumber);
            rankLotto.put(sameLottoNumber, rankLotto.getOrDefault(sameLottoNumber, RESET_NUMBER) + INITIAL_NUMBER);
        }
        return rankLotto;
    }

    private String checkCorrespondingLottoNumber(List<Integer> lottoNumber, List<Integer> lastWeekLottoNumber) {
        int sameLottoNumber = (int) lastWeekLottoNumber.stream().filter(lottoNumber::contains).count();
        if (isBonusNumber(lottoNumber, lastWeekLottoNumber, sameLottoNumber)) {
            return LottoPrice.getBonusSameLottoNumber();
        }
        return Integer.toString(sameLottoNumber);
    }

    private boolean isBonusNumber(List<Integer> lottoNumber, List<Integer> lastWeekLottoNumber, int sameLottoNumber) {
        return lottoNumber.contains(lastWeekLottoNumber.get(lastWeekLottoNumber.size() - INITIAL_NUMBER)) && sameLottoNumber == BONUS_COUNT;
    }
}

 

*LottoReturnRate

package domain;

import java.util.List;

public class LottoReturnRate {

    private static final int RESET_NUMBER = 0;
    private static final double MAKE_RETURN_RATE_DEVIDE_NUMBER = 100.0;
    private static final int MAKE_RETURN_RATE_MULTIPLE_NUMBER = 100;

    private final double lottoReturnRate;

    public LottoReturnRate(List<LottoRank> lottoRanks, int getLottoMoney) {
        this.lottoReturnRate = calculateLottoReturnRate(lottoRanks, getLottoMoney);
    }

    public double getLottoReturnRate() {
        return lottoReturnRate;
    }

    private double calculateLottoReturnRate(List<LottoRank> lottoRanks, int getLottoMoney) {
        List<Integer> lottoRankPrice = LottoPrice.getLottoPriceBundle();
        int sumOfLottoMoney = RESET_NUMBER;
        int count = RESET_NUMBER;
        for (LottoRank lottoRank : lottoRanks) {
            sumOfLottoMoney += lottoRank.getLottoRankNumber() * lottoRankPrice.get(count);
            count++;
        }
        double lottoReturnRate = (double) sumOfLottoMoney / getLottoMoney;
        return Math.floor(lottoReturnRate * MAKE_RETURN_RATE_MULTIPLE_NUMBER) / MAKE_RETURN_RATE_DEVIDE_NUMBER;
    }
}

 

*InputView

package view;

import java.util.Scanner;

public class InputView {

    private final Scanner input = new Scanner(System.in);

    public int getLottoMoney() {
        return input.nextInt();
    }

    public int getPassiveLottoCount() {
        return input.nextInt();
    }

    public String inputLottoNumber() {
        return input.next();
    }

    public int inputBonusBall() {
        return input.nextInt();
    }
}

 

*LottoRankDto

package view;

import domain.LottoPrice;

public class LottoRankDto {

    private final String lottoRank;
    private final int lottoRankerNumber;

    public LottoRankDto(String lottoRank, int lottoRankerNumber) {
        this.lottoRank = lottoRank;
        this.lottoRankerNumber = lottoRankerNumber;
    }

    @Override
    public String toString() {
        if (lottoRank.equals("5BONUS")) {
            return "5개 일치, 보너스 볼 일치(" + LottoPrice.getLottoPrice(lottoRank) + ")- " + lottoRankerNumber + "개";
        }
        return lottoRank + "개 일치 (" + LottoPrice.getLottoPrice(lottoRank) + ")- " + lottoRankerNumber + "개";
    }
}

 

*OutputView

package view;

import java.util.List;

public class OutputView {

    private static final int DEVIDE_LOTTO_COUNT_NUMBER = 1000;

    public void printGetLottoMoney() {
        System.out.println("구입금액을 입력해 주세요.");
    }

    public void printPassiveLottoCount() {
        System.out.println("\n수동으로 구매할 로또 수를 입력해 주세요.");
    }

    public void printPassiveLottoNumber() {
        System.out.println("\n수동으로 구매할 번호를 입력해 주세요.");
    }

    public void printLottoCount(int lottoCount, int passiveLottoNumberCount) {
        System.out.println("\n수동으로 " + passiveLottoNumberCount + "장, 자동으로 " + ((lottoCount / DEVIDE_LOTTO_COUNT_NUMBER) - passiveLottoNumberCount) + "개를 구매했습니다.");
    }

    public void printLotto(List<Integer> lottoNumber) {
        System.out.println(lottoNumber);
    }

    public void LastWeekLottoNumber() {
        System.out.println("\n" + "지난 주 당첨 번호를 입력해 주세요.");
    }

    public void inputBonusBall() {
        System.out.println("\n보너스 볼을 입력해주세요.");
    }

    public void printLottoStatistics() {
        System.out.println("\n당첨 통계");
        System.out.println("---------");
    }

    public void printLottoRank(String lottoRank) {
        System.out.println(lottoRank);
    }

    public void printRateOfReturn(double rateOfReturn) {
        System.out.println("총 수익률은 " + rateOfReturn + "입니다.");
    }
}

정리.

이번 로또는 정말 아쉬움이 많이 남았다..

원시값 포장과 일급컬렉션을 최대한 지키려고 많이 노력하였고, 새로운 기술들을 많이 써봐서 좋기도 했지만 아직도 enum과 dto 를 어떤 식으로 효율적으로 써야 할 지 감이 잡히지 않았다.. 특히 dto의 경우 dto를 쓰는 목적이 정확하게 어떤 객체를 전달하는지 나타내기 위해 쓰는 것이라고 생각하는데, 나는 이 목적을 잘 지키지 못한 것 같아 많은 아쉬움이 들었다.

또한, LottoRank 클래스가 너무 난잡하게 작성되어 있다는 사실을 깨달았다. 객체를 더 나누거나 stream 메서드를 공부해 사용하거나, 아니면 다른 방식으로 간결하고 효율적인 메서드를 작성해야 했다는 생각이 들었다. 하지만 이번 로또 미션을 통해 일급컬렉션, 원시값 포장, 테스트 코드 작성 등 정말 기본적이고 개념적인 부분에 있어서는 얻은 것이 많다고 생각들고, 앞으로 코드 작성 할 때 객체를 어떤식으로 나누어야 할 지에 대한 감각이 생긴 것 같아서 너무 좋다. 더 열심히 해야징.