본문 바로가기
kotlin

Lotto - 1. Lotto

by 쭈꾸마뇽 2021. 11. 7.

여름쯤해서 NextStep에서 하는 TDD, Clean Code with Java 12기에 참여했었다.  이 당시에는 자바를 이용해서 구현했는데 이번에 Kotlin으로 다시한번 구현해보았다.  이번 게시글에서는 기존에 구현했던 자바코드와 코틀린 코드가 어떻게 다른지 비교해보려 한다.

자바코드를 보고 코틀린 코드를 짠 것이 아니기 때문에 구현상 다른점이 존재합니다

Lotto

제일 처음부분인 Lotto 클래스이다.  Lotto 클래스는 로또를 구매했을 때 하나의 번호 세트(6개의 번호)를 구현한 클래스이다.  이 Lotto 객체의 생성자는 직접 로또번호를 입력하는 경우, 자동으로 구매하는 경우 2가지가 있다.

private static final List<Integer> lottoRange = Stream.iterate(1, n -> n + 1)
    .limit(45)
    .collect(Collectors.toList());

public static Lotto createLotto() {
    List<Integer> lottoNums = new ArrayList<>(lottoRange);
    Collections.shuffle(lottoNums);
    return new Lotto(lottoNums.stream()
        .limit(6)
        .collect(Collectors.toList()));
}

public static Lotto createLotto(List<Integer> manualLottoNum) {
    validateLottoNum(manualLottoNum);
    return new Lotto(manualLottoNum);
}
class Lotto(private val lottoNumbers: List<Number>) {
    constructor() : this(Stream.iterate(1) { t -> t + 1 }
        .limit(MAX_NUMBER)
        .map { Number(it) }
        .toList()
        .shuffled()
        .subList(0, LOTTO_NUMBER_COUNT)
    )
    
    init {
        require(lottoNumbers.distinct().size == LOTTO_NUMBER_COUNT) {
            "로또 번호의 갯수는 중복되지 않고 6개여야 합니다"
        }
    }
    ...
}

자바코드에서는 Lotto의 개별 번호를 래핑하진 않았고 객체 생성 또한 팩토리 메소드를 사용하였다.  자바 코드로 구현하던 당시에는 팩토리 메소드가 좀 더 세련되어 보였는데 최근에는 유틸성 메소드 외에는 최대한 static을 사용하지 않으려고 한다. 

 

두 코드를 보면 공통적으로 객체의 무결성을 확인하는 로직이 있다.  자바의 경우 validateLottoNum이라는 함수를 만들어 객체가 갖고 있는 로또 번호를 검증하고 코틀린의 경우 init의 require 함수를 이용해 검증한다.  코틀린은 init를 이용해 값을 초기화 할 수도 있고 검증할 수도 있다.

 

코틀린 코드에서는 생성자와 검증 함수를 이렇게 분리해서 작성 할 수 있다.


public int countCorrectNums(LastWeekLotto lastWeekLotto) {
    return (int) lottoNums.stream()
        .filter(lastWeekLotto::contains)
        .count();
}

public boolean isLottoNumContainsBonusNum(LastWeekLotto lastWeekLotto) {
    return lottoNums.stream()
    	.anyMatch(lastWeekLotto::isBonusNumCorrect);
}
fun countMatchNumbers(lastWeekLotto: LastWeekLotto): Int {
    return lottoNumbers.count { number ->
    	lastWeekLotto.contains(number)
    }
}

fun isBonusNumberCorrect(lastWeekLotto: LastWeekLotto): Boolean {
    return lottoNumbers.any { number ->
    	lastWeekLotto.isBonusNumberMatch(number)
    }
}

다음은 로또 번호와 당첨 번호 중 몇개가 일치하는지 판별하는 함수이다.  자바의 경우 stream을 이용해 filter처리한 뒤 count하는 람다 식을 사용했는데 코틀린은 더 간결한 람다 함수를 지원한다. 


private static final int LOTTO_NUM_SIZE = 6;
companion object {
    private const val MAX_NUMBER = 45L
    private const val LOTTO_NUMBER_COUNT = 6
    private const val COST = 1000
}

자바에서는 매직넘버를 상수로 관리하기 위해 static final을 사용한다.  여기서 단순히 final을 사용하지 않고 static을 붙이는 이유는 이 변수를 단순히 재할당만 막는 것이 아닌 상수화 하기 위함이다. 

 

자바의 경우 따로 코딩 규칙을 명확히 하지 않는다면 매직넘버와 인스턴스 변수가 섞여있어 한눈에 코드가 보이지 않는다.  반면에 코틀린은 companion object를 이용해 static처럼 사용하여 정적함수, 정적변수들을 한곳에서 관리 할 수 있다.


class Number(
    private val number: Int
) {
    companion object {
        private const val MIN_NUMBER = 1;
        private const val MAX_NUMBER = 45;
    }

    init {
        require(number in MIN_NUMBER..MAX_NUMBER) {
            "로또번호는 $MIN_NUMBER ~ $MAX_NUMBER 사이여야 합니다"
        }
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Number

        if (number != other.number) return false

        return true
    }

    override fun hashCode(): Int {
        return number
    }

    override fun toString(): String {
        return number.toString()
    }
}

자바 코드와는 다르게 로또 번호를 Number라는 클래스로 래핑해주었다.  이 코드에서는 Number로 클래스명을 지었지만 코틀린에는 Number라는 타입이 이미 존재하니 다른 클래스명을 지어주는 것이 낫다.

 

자바 코드에서는 로또 번호 검증 로직을 Lotto 클래스에 작성햇지만 이렇게 래핑을 하게 된다면 해당 로직을 래핑한 클래스로 옮겨올 수 있다.  때문에 클래스의 크기를 작게 만들 수 있다.  또한 이렇게 래핑할 경우 equals and hashcode를 재정의 해줘야 한다.  그래야 객체의 값을 제대로 비교할 수 있다.


소스코드 

 

GitHub - rkdals213/kotlin

Contribute to rkdals213/kotlin development by creating an account on GitHub.

github.com

'kotlin' 카테고리의 다른 글

Lotto - 2. Lottos (일급 컬렉션) / Shop  (0) 2021.11.09

댓글