스프링/미션

[Spring] lotto - 도메인 로직 구현

문상휘파람 2024. 10. 2. 10:38

도메인 로직 기능 구현 목록

 

저번 포스팅에서 작성했던 도메인 로직 기능 구현 목록입니다.

 

 

바로 제가 작성한 도메인에 대해 설명드리겠습니다.

 


먼저, 로또 숫자 생성에 관한 로직입니다.

 

* CreateRandomNumber

public interface CreateRandomNumber {

    int generateRandomNumber();
}

 

저는 숫자 하나 하나를 생성하는 인터페이스를 만들었습니다.

 

* LottoRandomNumber

public class LottoRandomNumber implements CreateRandomNumber {

    private static final Random createRandomNumber = new Random();
    private static final int MINIMUM = 1;
    private static final int MAXIMUM = 45;

    @Override
    public int generateRandomNumber() {
        return createRandomNumber.nextInt((MAXIMUM - MINIMUM) + MINIMUM) + MINIMUM;
    }
}

 

그리고 생성한 인터페이스를 상속받아 1~45 사이의 로또 숫자를 생성하는 구현체를 만들었습니다.


그리고 생성한 숫자를 원시값 포장하여 사용하기 위해 따로 클래스를 구현했습니다.

 

* Number

public class Number {

    private static final int MAXIMUM_LOTTO_NUMBER = 45;
    private static final int MINIMUM_LOTTO_NUMBER = 1;

    private final int number;

    public Number(int number) {
        this.number = number;
    }

    public static Number form(int number) {
        validateNumberRange(number);
        return new Number(number);
    }

    public int getNumber() {
        return number;
    }

    private static void validateNumberRange(int number) {
        if (number > MAXIMUM_LOTTO_NUMBER || number < MINIMUM_LOTTO_NUMBER) {
            throw new LottoRangeException();
        }
    }
}

 

검증로직을 구현하였으며, 정적 팩토리 메서드를 사용해 숫자를 감싸줄 수 있는 형태로 로직을 구현하였습니다.


그리고 가장 중요한 Lotto 객체입니다.

 

*Lotto

public Lotto(CreateRandomNumber createRandomNumber) {
    this.lotto = createLottoNumber(createRandomNumber);
}

 

createRandomNumber 클래스를 생성자 매개변수로 입력받아 로또 숫자가 생성될 수 있도록 하였습니다.

 

private List<Number> createLottoNumber(CreateRandomNumber createRandomNumber) {
    List<Number> numbers = new ArrayList<>();
    for (int i = INITIAL_NUMBER; i < LOTTO_NUMBER_BOUNDARY; i++) {
        Number number = selectNumber(createRandomNumber, numbers, Number.form(createRandomNumber.generateRandomNumber()));
        numbers.add(number);
    }
    return numbers;
}

private Number selectNumber(CreateRandomNumber createRandomNumber, List<Number> numbers, Number number) {
    List<Integer> lottoNumber = makeLottoNumbers(numbers);
    while (isSameNumberInLotto(lottoNumber, number)) {
        number = Number.form(createRandomNumber.generateRandomNumber());
    }
    return number;
}

private List<Integer> makeLottoNumbers(List<Number> numbers) {
    List<Integer> lottoNumber = new ArrayList<>();
    for (Number number : numbers) {
        lottoNumber.add(number.getNumber());
    }
    return lottoNumber;
}

private boolean isSameNumberInLotto(List<Integer> lottoNumber, Number number) {
    return lottoNumber.contains(number.getNumber());
}

 

로또가 6자리로 구성되어 있기 때문에 총 6번을 반복하여 숫자를 생성해서 리스트에 담을 수 있도록 구현하였고,

로또는 숫자가 겹치면 안되기에 숫자가 겹치는지 확인해주는 로직을 같이 구현하였습니다.


다음은 로또 숫자가 String 값으로 들어왔을 때, Integer 값으로 파싱해주는 클래스를 만들었습니다.

 

해당 객체를 구현한 이유는 다음과 같습니다.

저는 유저 로또 번호를 생성할 떄, 로또 당첨 번호 1개를 같이 생성할 생각입니다.

 

이후, 당첨 번호를 데이터베이스에 저장하고 필요할 때마다 꺼내 쓸 생각이기에 Integer 형으로 파싱해야 했기에 다음과 같은 객체를 구현하였습니다. 

 

*LottoNumberParser

private final List<Integer> lottoNumber;

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

 

역시 일급컬레션으로 관리하였습니다. List<Name> 형식으로 필드를 두지 않은 이유는, 어차피 서버 안에서만 돌아가는 로직이기도 하고 이미 검증된 숫자들이 당첨 번호로 생성된 것이기에 Name 값에 따로 담을 필요가 없다고 판단하였습니다. (이 부분에 관해서는 조언 주시면 감사하겠습니다..!)

 

private List<Integer> makeLottoNumber(String lottoNumber) {
    List<Integer> parsedLottoNumber = new ArrayList<>();
    for (String number : parseLottoNumber(cleanLottoNumber(lottoNumber))) {
        int realLottoNumberElement = Integer.parseInt(number.trim());
        parsedLottoNumber.add(realLottoNumberElement);
    }
    return parsedLottoNumber;
}

private String cleanLottoNumber(String lottoNumber) {
    return lottoNumber.replaceAll(DELETE_STRING_DELIMITER, "");
}

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

 

Integer 형으로 파싱해주는 로직입니다. 특별한 로직은 없습니다.


다음은 로또 맞춘 숫자를 반환해주는 클래스를 구현했습니다.

 

*LottoRank

private final int count;

public LottoRank(List<Integer> lottoNumber, List<Integer> lottoAnswer) {
    this.count = countLottoNumber(lottoNumber, lottoAnswer);
}

 

생성자에 유저 로또 번호와 당첨 번호를 넣어주면, 얼마나 맞았는지 도출해내는 로직을 구현했습니다.

 

private int countLottoNumber(List<Integer> lottoNumber, List<Integer> lottoAnswer) {
    int count = INITIAL_NUMBER;
    for (int number : lottoNumber) {
        count = checkCount(lottoAnswer, number, count);
    }
    return count;
}

private int checkCount(List<Integer> lottoAnswer, int number, int count) {
    if (lottoAnswer.contains(number)) {
        count++;
    }
    return count;
}

 

로또 번호가 얼마나 맞았는지 도출하는 로직입니다.


마지막으로 로또 맞은 개수를 관리해주는 클래스를 만들었습니다.

 

*LottoPrice

public enum LottoPrice {

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

    private static final int INITIAL_NUMBER = 0;
    private static final int LAST_PLACE = 3;

    private final int price;

    LottoPrice(int count, int price) {
        this.price = price;
    }

    public static int getLottoPrice(int count) {
        if(count < LAST_PLACE)
        {
            return INITIAL_NUMBER;
        }
        LottoPrice[] Prices = LottoPrice.values();
        return Prices[count - LAST_PLACE].getPrice();
    }

    private int getPrice(){
        return price;
    }
}

 

enum 형태로 관리하였습니다.

 

제가 해당 클래스를 구현한 이유는 다음과 같습니다. 원래는 그냥 로직에 숫자를 상수화 처리 하여 구현하였는데, 코드가 너무너무 더러웠습니다. 특히 가독성 면에서 좋지 않았습니다. 

 

그래서 멘토님이 enum 클래스를 활용해보면 어떻겠냐는 조언을 주셨고, enum에 대해 학습한 뒤 구현하였습니다.

 

가독성이 크게 좋아졌고, LottoRank 에서 맞은 개수만 반환하면 되었기에 의존성도 많이 줄였고 응집성이 높아졌습니다.


 

도메인 기능 구현 목록을 정리한 뒤, 구현해보았습니다. 

이 도메인들을 이용해 API 서버 로직에서 활용해보도록 하겠습니다! 감사합니다~~ :)