티스토리 뷰

728x90

17강. 연산자 오버로딩

연산자 오버로딩 특징

  1. operator키워드가 fun앞에 붙음
  2. 함수의 이름과 파라미터가 정해져있음

예시 - Point

data class Point (
    val x: Int,
    val y: Int
) {
    fun zeroPointSymmetry() = Point(-x, -y)
}

fun main() {
    val point = Point(20, -10)
    println(point.zeroPointSymmetry())
}

zeroPointSymmetry() 메서드를 통해 점대칭을 구하고 있음
하지만, 연산자 오버로딩을 통해 좀더 간단히 사용 가능

unaryMinus(): 단항 마이더스 연산자

data class Point (
    val x: Int,
    val y: Int
) {
    operator fun unaryMinus(): Point = Point(-x, -y)
}

fun main() {
    val point = Point(20, -10)
    println(-point)
}

inc(): 증가 연산자

data class Point (
    val x: Int,
    val y: Int
) {
    operator fun inc() = Point(x + 1, y + 1)
}

fun main() {
    var point = Point(20, -10)
    println(++point)
}

// Point(x=21, y=-9)

plus(): 덧셈 연산자

data class Days(val day: Long)

operator fun LocalDate.plus(days: Days): LocalDate {
    return this.plusDays(days.day)
}

// 확장 프로퍼티
val Int.days: Days
    get() = Days(this.toLong())

fun main() {
    println(LocalDate.now().plusDays(Days(1).day))
    println(LocalDate.now() + Days(1))
    println(LocalDate.now() + 3.days)
}

복합 대입 연산자 (+=)

적용 순서

flowchart TD
    A[복합 대입 연산자 시작] --> B{오버로딩 확인}
    B -->|오버로딩 있음| C[오버로딩 적용]
    B -->|오버로딩 없음| D{변수 타입 확인}
    D -->|var 변수| E[산술 연산자 적용]
    E --> F[변수 갱신]
    D -->|val 변수| G[에러 발생]
    C --> H[종료]
    F --> H
    G --> H

다양한 연산자들이 존재

  • == / != : equals
  • >, >=, <, <=: compareTo
  • List, Map 편의 기능도 get/set 연산자
    • list[1], map['key']

invoke(): 함수 호출도 하나의 연산자

  • 이전예시에 적용
    enum class Operator(
      private val oper: Char,
      val calcFun: (Int, Int) -> Int
    ) {
      PLUS('+', { a, b -> a + b }),
      MINUS('-', { a, b -> a - b }),
      MULTIPLY('*', { a, b -> a * b }),
      DIVIDE('/', { a, b ->
          if (b == 0) { throw IllegalArgumentException("0으로 나눌수 없습니다.") }
          a / b
      });
    
    
operator fun invoke(num1: Int, num2: Int): Int {
    return this.calcFun(num1, num2)
}

}

fun calculate(num1: Int, num2: Int, oper: Operator): Int = oper.calcFun(num1, num2)
fun calculateOperatorOverload(num1: Int, num2: Int, oper: Operator): Int = oper(num1, num2)


**연산자 오버로딩은 그 의미에 맞게 사용하는것이 중요!!**

---

# 18강. Kotlin DSL 직접 만들어보기
## DSL?
- Domain Specipic Language: 특정 목적을 위해 존재하는 언어

## 목표
- YAML을 렌더링하는 Kotlin DSL 만들기
```yaml
version: '3'
services:
  db:
    image: mysql
    environment:
      - USER: myuser
      - PASSWORD: mypassword
    port:
      - "9999:3306"

1단계

  • DockerCompose 클래스에 version 추가해서 yaml로 나타내기
  • class DockerCompose { private var version: Int by onceNotNull() fun version(init: () -> Int) { version = init() } fun render(indent: String): String { val builder = StringBuilder() builder.appendNew("version: '$version'") return builder.toString() } override fun toString(): String { return "DockerCompose(version=$version)" } }

fun onceNotNull() = object : ReadWriteProperty<Any?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (this.value == null) {
throw IllegalArgumentException("변수가 초기화되지 않았습니다.")
}

    return this.value!!
}

override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
    if (this.value != null) {
        throw IllegalArgumentException("이 변수는 한번만 초기화 가능합니다.")
    }

    this.value = value
}

}

fun dockerCompose(init: DockerCompose.() -> Unit): DockerCompose {
val dockerCompose = DockerCompose()
dockerCompose.init()
// or init(dockerCompose)
return dockerCompose
}

fun StringBuilder.appendNew(str: String, indent: String = "", times: Int = 0) {
(1..times).forEach { _ -> this.append(indent) }
this.append(str)
this.append("\n")
}

fun String.addIndent(indent: String, times: Int = 0): String {
val allIndent = (1..times).joinToString("") { indent }
return this.split("\n")
.joinToString("\n") { "$allIndent$it" }
}

fun main() {
val yml = dockerCompose {
version { 3 }
}

println(yml.render(" "))

}


## 2단계
- `service`추가하기
```kotlin
class DockerCompose {
    private var version: Int by onceNotNull()
    private val services = mutableListOf<Service>()

    fun version(init: () -> Int) {
        version = init()
    }

    fun service(name: String, init: Service.() -> Unit) {
        val service = Service(name)
        service.init()

        this.services.add(service)
    }

    fun render(indent: String): String {
        val builder = StringBuilder()
        builder.appendNew("version: '$version'")
        builder.appendNew("services:")
        builder.appendNew(services.joinToString("\n") { it.render(indent) }.addIndent(indent, 1))

        return builder.toString()
    }

    override fun toString(): String {
        return "DockerCompose(version=$version)"
    }
}

class Service(
    val name : String,
) {
    private var image: String by onceNotNull()
    fun image(init: () -> String) {
        this.image = init()
    }

    fun render(indent: String): String {
        val builder = StringBuilder()
        builder.appendNew("$name:")
        builder.appendNew("image: $image", indent, 1)
        return builder.toString()
    }

}

fun <T> onceNotNull() = object : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (this.value == null) {
            throw IllegalArgumentException("변수가 초기화되지 않았습니다.")
        }

        return this.value!!
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        if (this.value != null) {
            throw IllegalArgumentException("이 변수는 한번만 초기화 가능합니다.")
        }

        this.value = value
    }

}

fun dockerCompose(init: DockerCompose.() -> Unit): DockerCompose {
    val dockerCompose = DockerCompose()
    dockerCompose.init()
//  or  init(dockerCompose)
    return dockerCompose
}

fun StringBuilder.appendNew(str: String, indent: String = "", times: Int = 0) {
    (1..times).forEach { _ -> this.append(indent) }
    this.appe

3단계: env, port 추가

class DockerCompose {
    private var version: Int by onceNotNull()
    private val services = mutableListOf<Service>()

    fun version(init: () -> Int) {
        version = init()
    }

    fun service(name: String, init: Service.() -> Unit) {
        val service = Service(name)
        service.init()

        this.services.add(service)
    }

    fun render(indent: String): String {
        val builder = StringBuilder()
        builder.appendNew("version: '$version'")
        builder.appendNew("services:")
        builder.appendNew(services.joinToString("\n") { it.render(indent) }.addIndent(indent, 1))

        return builder.toString()
    }

    override fun toString(): String {
        return "DockerCompose(version=$version)"
    }
}

class Service(
    val name : String,
) {
    private var image: String by onceNotNull()
    private val environments = mutableListOf<Environment>()
    private val portRules = mutableListOf<PortRule>()

    fun image(init: () -> String) {
        this.image = init()
    }

    fun env(environment: Environment) {
        this.environments.add(environment)
    }

    fun port(host: Int, container: Int) {
        this.portRules.add(PortRule(host, container))
    }

    fun render(indent: String): String {
        val builder = StringBuilder()
        builder.appendNew("$name:")
        builder.appendNew("image: $image", indent, 1)
        builder.appendNew("environment:")
        builder.appendNew(
            environments
                .joinToString("\n") { "- ${it.key}: ${it.value}" }
                .addIndent(indent, 2)
        )

        builder.appendNew("port:")
        portRules.joinToString("\n") { "- \"${it.host}:${it.container}\"" }
            .addIndent(indent, 2)
            .also { builder.appendNew(it) }


        return builder.toString()
    }
}

data class Environment (
    val key : String,
    val value : String
)

data class PortRule (
    val host: Int,
    val container: Int
)

operator fun String.minus(other: String): Environment {
    return Environm

@DslMarker

  • 가장 가까운 수신객체에 대해서만 this를 생략 가능
    dockerCompose { 
        service(name = "db") {
            service(name = "db_another") {

            }
        }
    }
  • dockerCompose하위 어떤 계층이라도 service() 호출가능
    • 문법적으로는 가능하지만 계층적으로는 어색함
@DslMarker
annotation class YamlDsl


@YamlDsl
class DockerCompose {

}

@YamlDsl
class Service() {}


dockerCompose {
    service(name = "db") {
        service(name = "db_another") { // error!!
            // 가장 가까운 수신객체의 this만 사용가능
            // 명시적으로 상위 계층의 this를 사용하고 싶다면 this@dockerCompose 로 사용해야함
        }
    }
}

위 내용은 인프런 코틀린 고급편강의를 시청하고 작성했습니다
인프런
728x90
댓글