본문 바로가기

강의/TDD, Clean Code with Java 12기

[로또] step2 - 로또(자동) - 피드백 및 피드백 반영(21일차)

해당 변수들은 생성자를 통해 초기화하고 있는데요
생성자를 통한다는 것은 인스턴스화를 한다는 것인데, 모든 인스턴스가 공유하는 자원을 생성자로 초기화하는 설계에 어떠한 문제가 있을지 고민해보면 좋겠습니다!

추후 해당 서비스를 웹서비스로 공개하여 실제 로또 서비스를 여러 유저들이 사용할 수 있게 구현한다는 생각으로 접근해보면 좋을 것 같아요 :)

생성자에서 초기화를 하는 것에 대한 문제는,
클래스를 사용할 때 무조건 해당 변수를 인자로 넣어야 한다는 부담감이 생기는 것에 대한 문제 일까요??
LottoMachine에 있던 lastWinningLottoNumberChecker 클래스를 밖으로 빼고, 생성자에서 초기화 하던 부분을 수정하였습니다.

도메인 내에 View의 로직을 가지고 있다면 어떠한 문제가 있을지 고민해보아요

기능상에는 문제가 없을 것으로 판단이 되지만,
해당 도메인을 사용하기 위해서는 항상 ResultView라는 클래스가 존재해야 하는 문제점이 발생할 것 같습니다.
이러한 문제 때문에 분리를 하는 것이 옳다고 생각 됩니다.
멘토님은 어떻게 생각하시나요??

lotto 번호가 생성되자 마자 print 해주는 것이 더욱 효율적인 부분도 존재한다고 생각하는데... 아닐까요??
lotto 번호를 반환해서 다시 resultview에 print해줘! 이렇게 명령하는게 좀 비효율적인 부분도 있다고 생각합니다.
이러한 경우에도 꼭 분리해주는 것이 좋을까요??

"규칙 3. 모든 원시값과 문자열을 포장한다."에 대해 열심히 적용해주셨네요 👍
지금은 전체적으로 static을 사용한 코드를 사용하고 있어 객체 자체가 자신의 역할을 제대로 못하는 경우가 존재하네요!
"규칙 3. 모든 원시값과 문자열을 포장한다."에서 핵심은 자기 자신의 형태와 역할을 분리하는 것으로 볼 수 있는데요

예를 들어 String 클래스가 문자열의 형태를 보장하고 기능을 제공하고,
Ineger 클래스가 정수형의 형태를 보장하고 기능을 제공하는 것 처럼,
LottoNumber가 1~45의 숫자임을 보장한다! 와 같은 방식으로 접근해보면 좋을 것 같아요 ㅎㅎ'

숫자임을 보장하기 위해 LottoNumber 라는 클래스를 만들어서 포장해 보았습니다.
멘토님도 저와 동일한 방법으로 진행하셨는지 궁금합니다! (멘토님이 의도하신 대로 제가 구현을 한 것이 맞는지 의구심이 드네요 ㅜㅜ)

좋은 피드백을 받아서 너무 즐겁네요!
오늘도 정말 감사합니다!!

 

 

package step2.domain;

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

public class Lotto {
    static final int MIN_OF_LOTTO_NUMBER = 1;
    static final int MAX_OF_LOTTO_NUMBER = 45;
    static final int NUM_OF_LOTTO_NUMBER = 6;
    static ArrayList<Integer> lottoNumberList = new ArrayList<>();
    static LottoNumber lottoNumber;


    public Lotto() {
        makeLottoNumberList();
    }

    public ArrayList<Integer> issueLotto() {
        ArrayList<Integer> lotto = new ArrayList<>();
        Collections.shuffle(lottoNumberList);
        for (int i = 0; i < NUM_OF_LOTTO_NUMBER; i++) {
            lotto.add(lottoNumberList.get(i));
        }
        return lotto;
    }

    public ArrayList<Integer> makeLottoNumberList() {
        for (int i = MIN_OF_LOTTO_NUMBER; i <= MAX_OF_LOTTO_NUMBER; i++) {
            lottoNumber = new LottoNumber(i);
            lottoNumberList.add(lottoNumber.getNumber());
        }
        return lottoNumberList;
    }
}
package step2.domain;

import java.util.ArrayList;

public class LottoMachine {
    static Lotto lotto = new Lotto();
    static ArrayList<ArrayList> issuedLottolist = new ArrayList<>();


    public ArrayList<ArrayList> run(int numOfLotto) {
        getLottoList(numOfLotto);

        return issuedLottolist;
    }

    public ArrayList<ArrayList> getLottoList(int numOfLotto) {
        for (int i = 0; i < numOfLotto; i++) {
            issuedLottolist.add(lotto.issueLotto());
        }
        return issuedLottolist;
    }
}
package step2.domain;

public class LottoNumber {
    private final int number;
    private static final String LOTTO_NUMBER_RANGE_ERROR_MESSAGE = "로또 당첨번호는 1과 45 사이의 정수이어야 합니다.";
    private static final int MIN_OF_LOTTO_NUMBER = 1;
    private static final int MAX_OF_LOTTO_NUMBER = 45;

    public LottoNumber(int number) {
        checkRange(number);
        this.number = number;
    }

    private void checkRange(int number) {
        if (number > MAX_OF_LOTTO_NUMBER || number < MIN_OF_LOTTO_NUMBER) {
            throw new IllegalArgumentException(LOTTO_NUMBER_RANGE_ERROR_MESSAGE);
        }
    }

    public int getNumber() {
        return number;
    }
}
package step2.util;

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

public class Calculator {
    static ArrayList<ArrayList> issuedLottolist;
    static final int NUM_OF_LOTTO_NUMBERS = 6;
    static int[] lottoResultArray;
    static ArrayList<Integer> lastWinningLottoNumberArray;

    public Calculator(ArrayList<ArrayList> issuedLottolist, ArrayList<Integer> lastWinningLottoNumberArray) {
        this.issuedLottolist = issuedLottolist;
        this.lastWinningLottoNumberArray = lastWinningLottoNumberArray;
    }

    public void run() {
        lottoResultArray = new int[NUM_OF_LOTTO_NUMBERS+1];
        for (ArrayList<Integer> issuedLotto : issuedLottolist) {
            calcMatchedLottoNum(issuedLotto);
        }
    }

    public void calcMatchedLottoNum(ArrayList<Integer> issuedLotto) {


        List<Integer> matchedLottoList = issuedLotto.stream()
                .filter(num -> lastWinningLottoNumberArray.contains(num))
                .collect(Collectors.toList());
        lottoResultArray[matchedLottoList.size()]++;
    }

    public int[] getLottoResultArray() {
        return lottoResultArray;
    }


}
package step2.util;

import java.util.ArrayList;

public class LastWinningLottoNumberChecker {
    private static final String LOTTO_COUNT_ERROR_MESSAGE = "로또 당첨번호는 6개를 입력하세요. 당첨번호는 ', '로 구분을 하고 있습니다.";
    private static final String LOTTO_NUMBER_RANGE_ERROR_MESSAGE = "로또 당첨번호는 1과 45 사이의 정수이어야 합니다.";
    private static final String LOTTO_DELIMITER = ", ";
    private static final int MIN_OF_LOTTO_NUMBER = 1;
    private static final int MAX_OF_LOTTO_NUMBER = 45;
    private static final int COUNT_OF_LOTTO_WINNING_NUMBER = 6;
    private final ArrayList<Integer> lastWinningLottoNumberArray;

    public LastWinningLottoNumberChecker(String lastWinningLottoNum) {
        ArrayList<Integer> lottoNumberIntegerArray = makeIntegerArray(validateCountOfLotto(lastWinningLottoNum));
        lottoLoop(lottoNumberIntegerArray);
        this.lastWinningLottoNumberArray = lottoNumberIntegerArray;
    }

    private ArrayList<Integer> makeIntegerArray(String[] lottoNumberStringArray) {
        ArrayList<Integer> lottoNumberIntegerArray = new ArrayList<>();
        for (int i = 0; i < lottoNumberStringArray.length; i++) {
            lottoNumberIntegerArray.add(Integer.parseInt(lottoNumberStringArray[i]));
        }
        return lottoNumberIntegerArray;
    }

    public void lottoLoop(ArrayList<Integer> lottoNumberIntegerArray) {
        for (int lottoNumber : lottoNumberIntegerArray) {
            validatePositiveLottoNum(lottoNumber);
        }
    }

    private void validatePositiveLottoNum(int lottoNumber) {
        if (lottoNumber < MIN_OF_LOTTO_NUMBER || lottoNumber > MAX_OF_LOTTO_NUMBER) {
            throw new IllegalArgumentException(LOTTO_NUMBER_RANGE_ERROR_MESSAGE);
        }
    }

    public String[] validateCountOfLotto(String lastWinningLottoNum) {
        String[] lottoNumberStringArray = lastWinningLottoNum.split(LOTTO_DELIMITER);
        int numOfLotto = lottoNumberStringArray.length;
        if (numOfLotto != COUNT_OF_LOTTO_WINNING_NUMBER) {
            throw new IllegalArgumentException(LOTTO_COUNT_ERROR_MESSAGE);
        }
        return lottoNumberStringArray;
    }

    public ArrayList<Integer> getLottoWinningNumberArray() {
        return lastWinningLottoNumberArray;
    }
}
package step2.view;

import java.util.Scanner;

public class InputView {
    static final String INPUT_MESSAGE = "구입 금액을 입력해주세요.";
    static final String INPUT_INFO_MESSAGE = "개를 구매했습니다.";
    static final String INPUT_LAST_LOTTO_NUMBER = "지난 주 당첨 번호를 입력해 주세요.";
    private static final int chargeOfLotto = 1000;
    static Scanner sc = new Scanner(System.in);

    public int requestInput() {
        System.out.println(INPUT_MESSAGE);
        int charge = sc.nextInt();
        sc.nextLine();
        int numOfLotto = charge/chargeOfLotto;
        System.out.println(numOfLotto+INPUT_INFO_MESSAGE);
        return numOfLotto;
    }

    public String requestWinningLottoNumber() {
        System.out.println(INPUT_LAST_LOTTO_NUMBER);
        String lastWinningLottoNum = sc.nextLine();
        return lastWinningLottoNum;
    }
}
package step2.view;

import java.util.ArrayList;

public class ResultView {
    static int[] charge = {0,0,0,5000,50000,1500000,2000000000};

    public void printMatchedLottoRecord(int[] matchedLottoRecordArray, int numOfLotto) {
        int totalRevenue = 0;
        System.out.println("당첨 통계");
        System.out.println("-------");
        for (int i = 1; i <= 6; i++) {
            System.out.printf("%s 개 일치 (%s) %s개\n",i,charge[i],matchedLottoRecordArray[i]);
            totalRevenue += charge[i] * matchedLottoRecordArray[i];
        }

        double yield =  totalRevenue / (double)(numOfLotto * 1000);
        printResultMessage(yield);

    }

    private void printResultMessage(double yield) {
        if (yield < 1) {
            System.out.printf("총 수익률은 %s입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)",yield);
            return;
        }
        if (yield >= 1) {
            System.out.printf("총 수익률은 %s입니다.(기준이 1이기 때문에 결과적으로 이익이라는 의미임)",yield);
            return;
        }

    }

    public void printIssuedLottoList(ArrayList<ArrayList> issuedLottolist) {
        issuedLottolist.stream()
                .forEach(issuedLotto -> System.out.println(issuedLotto));
    }
}
package step2;

import step2.domain.LottoMachine;
import step2.util.Calculator;
import step2.util.LastWinningLottoNumberChecker;
import step2.view.InputView;
import step2.view.ResultView;

import java.util.ArrayList;

public class LottoApp {
    public static void main(String[] args) {
        InputView inputView = new InputView();
        ResultView resultView = new ResultView();

        int numOfLotto = inputView.requestInput();

        LottoMachine lottoMachine = new LottoMachine();
        ArrayList<ArrayList> issuedLottolist = lottoMachine.run(numOfLotto);
        resultView.printIssuedLottoList(issuedLottolist);

        String lastWinningLottoNum = inputView.requestWinningLottoNumber();

        LastWinningLottoNumberChecker lastWinningLottoNumberChecker;
        lastWinningLottoNumberChecker = new LastWinningLottoNumberChecker(lastWinningLottoNum);
        ArrayList<Integer> lastWinningLottoNumberArray = lastWinningLottoNumberChecker.getLottoWinningNumberArray();

        Calculator calculator = new Calculator(issuedLottolist, lastWinningLottoNumberArray);
        calculator.run();
        int[] lottoResultArray = calculator.getLottoResultArray();
        resultView.printMatchedLottoRecord(lottoResultArray, numOfLotto);
    }
}