2. Conventions

Convention 에는 "관례" 라는 뜻이 있다...

일단 귀찮으니 넘어가자.

 

1) Comparison

첫 번째 문제는 Comparison 이라는 문제다.

여기서 핵심은 

operator 를 overriding 할 수 있다는 개념이다.

 

클래스 내에서 함수를 override 할 때는 operator 키워드를 붙여줄 필요는 없다.

 

Comparable 을 상속해서 , compareTo 함수를 오버라이딩 해주면 끝.

 

이 compareTo 는 operator 에서 <  ==  > 등을 비교하는데 쓰인다.

 

2) In Range

 

이 문제는 보자마자 뭔 개소리지?? 하는 느낌을 주었다.

애초에 자바에 in 같은 키워드가 없기 때문이다. (적어도 난 안쓴다.)

 

쿼리로 따지면 Between 같은 느낌의 연산자이다.

 

"연산자" 이다. < > == 같은 연사자.

 

따라서 연산자 오버라이딩을 하면 된다.

 

Range

A range defines a closed interval in the mathematical sense: it is defined by its two endpoint values which are both included in the range. Ranges are defined for comparable types: having an order, you can define whether an arbitrary instance is in the range between two given instances. The main operation on ranges is contains, which is usually used in the form of in and !in operators.

To create a range for your class, call the rangeTo() function on the range start value and provide the end value as an argument. rangeTo() is often called in its operator form ...

 

class DateRange(val start: MyDate, val endInclusive: MyDate){
    operator fun contains(item: MyDate): Boolean =
            item >= start && item <= endInclusive
}

fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean {
    return date in DateRange(first, last)
}

이 문제는 이런 식으로 풀면 된다.

 

DateRange 라는 클래스를 만들고, 이 클래스의 main constructor 는 start 와 endinclusive 를 받는다.

그리고 자기가 갖고 있는 start와 endEnclusive 로 넘어오는 MyDate instance 가 범위 안에 속하는지 아닌지를 반환하는 contains 함수를 정의 해준다. 

 

여기서 알 수 있는 것은, "operator" 키워드를 사용해서, 타 클래스의 연산에 관여할 수 있는 클래스를 만들 수 있다는 것이다.

또한 MyDate는 이 전 문제 (1번) 에서 재 정의한 compareTo 함수를 갖고 있다는 것을 명심하자.

 

1) compareTo  로 대소를 배교할 수 있는 클래스(MyDate) 를 만들게 됨.

2) 이제 대소를 비교할 수 있으니 contains 함수도 만들 수 있음. in 연산자를 오버라이딩 한 클래스가 대신 이것을 처리 해 줌.

 

 

3) RangeTo

 

확장 함수로 연산자 오버로딩을 할 수 있다.

이 전 2번 문제에서는 

date in DateRange(first,last) ... 방식으로 범위 안에 속하는지를 검사 했지만.

 

date in first..last 형식으로 이를 바꾸는 것이다.

 

date 도 first 도 last 도 모두 MyDate 클래스의 인스턴스이다.

 

.. 을 사용하기 위해서는 rangeTo operator 를 만들어 줘야 한다.

 

operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

class DateRange(override val start: MyDate, override val endInclusive: MyDate): ClosedRange<MyDate>

 

위와 같이, operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other)

로 만들면 된다.

 

2단원 부터 갑자기 난이도가 올라가는 면이 있는데, 대부분이 오버라이딩에 관련된 문제인데 함수의 원형을 알기 힘들기 때문이다.

 

일단 문서(docs)를 타고 올라가서 rangeTo 의 틀을 찾아보자.

 

 

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.ranges/index.html

 

kotlin.ranges - Kotlin Programming Language

 

kotlinlang.org

여기에 가서 rangeTo 를 찾아보자.

 

ClosedRange 타입의 인스턴스를 반환해야 함을 알 수 있다.

 

DateRange 클래스는 현재 ClosedRange<MyDate> 를 상속하고 있다.

따라서 DateRange 인스턴스를 만들어서 반환하면 되는구나? 라는 것을 생각할 수 있다.

 

여기서 다시 정리해보자.

 

1. compareTo  로 MyDate 클래스의 인스턴스는 대소비교가 가능해짐

2. DateRange 라는 클래스로 contains 를 오버로딩 하면 in 연산자를 사용할 수 있음.

3. rangeTo 를 만들면 first..last 형식으로 쓸 수 있음. rangeTo 의 반환형은 뭐다? -> ClosedRange 다.

 

그럼 쉽게 예상할 수 있을 것이다. ClosedRange 를 상속 받으면 무엇을 오버라이딩 해줘야 할까? -> contains다.

결국 우리가 2번에서 만든 contains 함수를 갖는, DateRange instance를 반환하게 하는 것이 rangeTo 다.

 

따라서..

 

date in DateRange(first,last)  = date in first..last

 

first..last = first.ragneTo(last) = DateRange(first, last)

 

4) For loop

in 은 단순히 대소 비교를 통해, 범위 안에 값이 들어가는지 확인만 하는 것은 아니다. (아닌가?_

 

for 문 안에서도 사용될 수 있다.

 

예를 들면

 

for(date in firstDate..secondDate) {

 

}

와 같이 사용된다.

 

하지만 여기서는 같은 in 키워드여도 다른 것이 필요하다. 단순한 ClosedRange 가 아닌, Iterable 을 상속해야 하며.

Iterabe 인터페이스를 구현하면, Iterator를 반환해야 하고.

 

이 Iterator를 반환하기 위해 새로운 클래스를 만들어야 한다.

 

그냥 Iterator를 anonymous object로 전달하자 무한 루프에 빠져 버렸다. (다른 요인일 수도 있다.)

anonymous object로 만들지 말고, 클래스를 하나 만들고, 인스턴스로 Iterator를 하나 만들어서 뱉어주니 잘 작동했다.

 

 

class DateRange(val start: MyDate, val end: MyDate): Iterable<MyDate>{
    override fun iterator(): Iterator<MyDate> = DateIterator(this)
}

class DateIterator(val dateRange:DateRange) : Iterator<MyDate> {
    var current: MyDate = dateRange.start
    override fun next(): MyDate {
        val result = current
        current = current.nextDay()
        return result
    }
    override fun hasNext(): Boolean = current <= dateRange.end
}

fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) {
    for (date in firstDate..secondDate) {
        handler(date)
    }
}

 

이런 식으로 작서 ㅇ되는데...

 

firstDate..secondDate 를 보면 알 수 있듯이, rangeTo 연산자를 오버라이딩 했다.

rangeTo 함수는 이전 3번처럼 DateRange 인스턴스를 반환하다. DateRange 는 Iterable 을 구현한 클래스이다.

 

Iterator 는 next 와 hasNext 함수를 상속해줘야 한다. 살짝 IntelliJ의 자동 완성 기능의 도움을 받으면 쉽게 오버라이딩 할 함수의 틀을 만들 수 있다.

 

5) Operator overloading

 

오버로딩과 오버라이딩의 차이는 잘 알 것이다.

 

보통 부모의 함수를 상속받아, 자기 껄로 대체 하는것을 오버라이딩이라고 하고.

함수 이름은 같은데 매개변수가 다른 여러 함수를 만드는 것을 오버로딩이라고 한다.

 

오버라이딩은 런타임 시에, 오버로딩은 컴파일 시에 정의 된다... 

 

간략히 여기 까지만 설명 하고.

 

연산자 오버라이딩에서 왜 갑자기 연산자 오버로딩이 제목이 된 걸까? 이를 활용하는 것이 이번 문제의 핵심이자. 정말 재밌는 문제다.

 

난 못 풀었다.

 

6) Destructing declarations.

 

자바스크립트에 "구조 분해 할당" 이란 것이 있다.

 

그거랑 비슷 한건데,

fun isLeapDay(date: MyDate): Boolean {

val (year, month, dayOfMonth) = date

// 29 February of a leap year
return year % 4 == 0 && month == 2 && dayOfMonth == 29
}

 

이렇게 있을 때 MyDate 의 필드 값들이 year, month, dayOfMonth 에 분해돼 들어가는 느낌을 주는... 

 

이것 또한 연산자로 지정해 줘야 한단다...

 

다음과 같다.

 

class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) {
    operator fun component1(): Int {
        return year
    }
    operator fun component2(): Int {
        return month
    }
    operator fun component3(): Int {
        return dayOfMonth
    }
}

componentN() 을 정의해주면 된다.

N 에 1~N 까지으 숫자가 들어간다.

 

6) Invokable

 

별 거 없는 문제인데 한 번 기억해두면 좋을 요소가 하나는 있다.

 

class Invokable {
    var numberOfInvocations: Int = 0
        private set
    operator fun invoke(): Invokable {
        numberOfInvocations++
        return this
    }
}

fun invokeTwice(invokable: Invokable) = invokable()()

짜잔.

 

invoke 라는 연산자를 오버라이딩하면..

인스턴스에다가 () 를 붙여서 함수처럼 쓸 수 있다...  위에 invokeTwice 는 말 그대로 invoke 두번 한거다.

 

여기서 private set 부분을 한번 보고 넘어가자.

 

 

Kotlin 에서는 클래스 안에 var a = 3 이런 식으로 "필드" 를 선언해 놓으면 자동으로

get 과 set 함수가 생긴다.

 

자바에서는 아마 @Getter @Setter 등을 붙이거나. (lombok 사용 시에) 직접 만들어야 했을 것이다.

 

만약 상수 선언을 의미하는 val 을 쓴다면? setter 가 생기지 않는다.

 

위에서 필드 선언 부분 아래에서 private set 부분을 확인할 수 있다.

set 함수를 private으로 만든 것이다. var 로 선언했기 때문에 값이 변할 수는 있지만, 이 값을 외부에서 직접 특정 값으로 수정하지는 못하게 한 것이다.

 

이 부분만 집고 넘어가자.

 

2단원은 이렇게 마친다.

 

블로그 이미지

맛간망고소바

,