Chapter 5 객체 생성

μ½”ν‹€λ¦°μ˜ μ½”λ“œλŠ” 순수 ν•¨μˆ˜ν˜• μŠ€νƒ€μΌλ‘œ μž‘μ„±ν•  수 μžˆμ§€λ§Œ, μžλ°”μ²˜λŸΌ 객체 지ν–₯ ν”„λ‘œκ·Έλž˜λ°(Object Oriented Programming, OOP) μŠ€νƒ€μΌλ‘œλ„ μž‘μ„±ν•  수 μžˆλ‹€.

OOPλŠ” 객체λ₯Ό μƒμ„±ν•΄μ„œ μ‚¬μš©ν•˜λ―€λ‘œ, 객체λ₯Ό μƒμ„±ν•˜λŠ” 방법을 μ •μ˜ν•΄μ•Ό ν•˜κ³ , 이에 λŒ€ν•œ 방법듀과 μž₯단점을 μ‚΄νŽ΄λ΄μ•Ό ν•œλ‹€.

λ˜ν•œ 코틀린은 정적 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•  수 μ—†λ‹€.

κ·Έλž˜μ„œ 일반적으둜 ν†±λ ˆλ²¨ ν•¨μˆ˜μ™€ companion 객체 ν•¨μˆ˜ 등을 λŒ€μ‹  ν™œμš©ν•œλ‹€.

μ΄λ“€μ˜ μž‘λ™λ°©μ‹μ—λŠ” 큰 차이가 μžˆμœΌλ―€λ‘œ μ•Œμ•„λ³΄λ„λ‘ ν•˜μž.

Item 33 μƒμ„±μž λŒ€μ‹  νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λΌ

클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“œλŠ” κ°€μž₯ 기본적인 방법은 κΈ°λ³Έ μƒμ„±μž(primary constructor) λ₯Ό μ‚¬μš©ν•˜μ—¬ λ§Œλ“œλŠ” 방법이닀.

class MyLinkedList<T>(
  val head: T,
  val tail: MyLinkedList<T>?
)

val list = MyLinkedList(1, MyLinkedList(2, null))

μƒμ„±μžκ°€ 객체λ₯Ό λ§Œλ“€ 수 μžˆλŠ” μœ μΌν•œ 방법은 μ•„λ‹ˆλ©°, λ””μžμΈ νŒ¨ν„΄ 쀑 생성 νŒ¨ν„΄(creational pattern)을 μ΄μš©ν•˜μ—¬ λ§Œλ“€ 수 μžˆλ‹€.

생성 νŒ¨ν„΄μ€ 객체λ₯Ό μƒμ„±μžλ‘œ 직접 μƒμ„±ν•˜μ§€ μ•Šκ³ , λ³„λ„μ˜ ν•¨μˆ˜λ₯Ό 톡해 μƒμ„±ν•œλ‹€.

fun <T> myLinkedListOf(
  vararg elements: T
): MyLinkedList<T>? {
  if (elements.isEmpty()) return null
  val head = elements.first()
  val elementsTail = elements.copyOfRange(1, elements.size)
  val tail = myLinkedListOf(*elementsTail)
  return MyLinkedList(head, tail)
}

val list = myLinkedListOf(1, 2)

객체 μΈμŠ€ν„΄μŠ€ 생성을 μœ„ν•œ νŒ¨ν„΄ 쀑 νŒ©ν† λ¦¬ λ©”μ„œλ“œ ν΄λž˜μŠ€μ—μ„œ μƒμ„±μžμ˜ μ—­ν™œμ„ λŒ€μ‹  ν•΄ μ£ΌλŠ” ν•¨μˆ˜λ₯Ό νŒ©ν† λ¦¬ ν•¨μˆ˜λΌκ³  ν•œλ‹€.

νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ΄μš©ν–ˆμ„ λ•Œμ˜ λ‹€μŒκ³Ό 같은 μž₯점이 μžˆλ‹€.

  • ν•¨μˆ˜μ˜ 이름을 λΆ™μΌμˆ˜ μžˆλ‹€
    • μƒμ„±μžλ₯Ό μ΄μš©ν•˜μ—¬ μƒμ„±ν•˜λŠ”κ²ƒ 보닀 훨씬 μ΄ν•΄ν•˜κΈ° 쉽닀.(ArrayList.withSize(3))
    • λ™μΌν•œ νŒŒλΌλ―Έν„° νƒ€μž…μ„ κ°–λŠ” μƒμ„±μžμ˜ μΆ©λŒμ„ 쀄일 수 μžˆλ‹€.
  • μ›ν•˜λŠ” νƒ€μž…μ˜ 객체λ₯Ό 리턴할 수 μžˆλ‹€.
    • μƒμ„±μžμ™€ λ‹€λ₯΄κ²Œ ν•¨μˆ˜κ°€ μ›ν•˜λŠ” ν˜•νƒœμ˜ νƒ€μž…μ„ 리턴할 수 μžˆλ‹€.
  • 객체 생성에 λ”°λ₯Έ μ „λž΅μ„ μ„ΈμšΈ 수 μžˆλ‹€.
    • μƒμ„±μžμ™€ λ‹€λ₯΄κ²Œ 호좜될 λ•Œλ§ˆλ‹€ μƒˆ 객체λ₯Ό λ§Œλ“€ ν•„μš”κ°€ μ—†λ‹€.
    • μ‹±κΈ€ν„΄ νŒ¨ν„΄μ²˜λŸΌ 객체λ₯Ό ν•˜λ‚˜λ§Œ μƒμ„±ν•˜κ²Œ κ°•μ œ ν•˜κ±°λ‚˜, μ΅œμ ν™”λ₯Ό μœ„ν•΄ 캐싱 λ©”μ»€λ‹ˆμ¦˜μ„ μ‚¬μš©ν•  수 μžˆλ‹€.
    • 객체λ₯Ό λ§Œλ“€ 수 없을 경우 null 을 λ¦¬ν„΄ν•˜κ²Œ λ§Œλ“€ 수 μžˆλ‹€.
  • νŒ©ν† λ¦¬ ν•¨μˆ˜λŠ” 아직 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 객체λ₯Ό 리턴할 수 μžˆλ‹€.
    • ν”„λ‘œμ νŠΈλ₯Ό λΉŒλ“œν•˜μ§€ μ•Šκ³  μ•žμœΌλ‘œ λ§Œλ“€μ–΄μ§ˆ 객체λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
    • ν”„λ‘μ‹œλ₯Ό 톡해 λ§Œλ“€μ–΄μ§€λŠ” 객체λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
  • 객체 외뢀에 νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό λ§Œλ“€λ©΄, κ·Έ κ°€μ‹œμ„±μ„ μ›ν•˜λŠ”λŒ€λ‘œ μ œμ–΄ν•  수 μžˆλ‹€.
  • νŒ©ν† λ¦¬ ν•¨μˆ˜λŠ” 인라인으둜 λ§Œλ“€μˆ˜ 있으며, κ·Έ νŒŒλΌλ―Έν„°λ₯Ό reified 둜 λ§Œλ“€μˆ˜ μžˆλ‹€.
  • νŒ©ν† λ¦¬ ν•¨μˆ˜λŠ” μƒμ„±μžλ‘œ λ§Œλ“€κΈ° λ³΅μž‘ν•œ 객체도 생성이 κ°€λŠ₯ν•˜λ‹€.
  • μ‚¬μš©μžκ°€ μ›ν•˜λŠ” μ‹œμ μ— 객체λ₯Ό λ§Œλ“€μˆ˜ μžˆλ‹€.

νŒ©ν† λ¦¬ ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œλŠ” μƒμ„±μžλ₯Ό μ΄μš©ν•˜μ—¬ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

νŒ©ν† λ¦¬ ν•¨μˆ˜λŠ” κΈ°λ³Έ μƒμ„±μžκ°€ μ•„λ‹Œ μΆ”κ°€ μƒμ„±μž(secondary constructor) 와 κ²½μŸκ΄€κ³„ 이닀.

Companion 객체 νŒ©ν† λ¦¬ ν•¨μˆ˜

νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜λŠ” κ°€μž₯ 일반적인 방법은 companion 객체λ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

class MyLinkedList<T>(
  val head: T,
  val tail: MyLinkedList<T>?
) {
  companion object {
    fun <T> of(vararg elements: T): MyLinkedList<T>? {
      /*...*/
    }
  }
}

val list = MyLinkedList.of(1, 2)

μ΄λŸ¬ν•œ 방법은 μΈν„°νŽ˜μ΄μŠ€μ—λ„ κ΅¬ν˜„ν•  수 μžˆλ‹€.

class MyLinkedList<T>(
  val head: T,
  val tail: MyLinkedList<T>?
): MyList<T> {
  //...
}

interface MyList<T> {
  //...

  companion object {
    fun <T> of(vararg elements: T): MyList<T>? {
      //...
    }
  }
}

val list = MyList.of(1, 2)

νŒ©ν† λ¦¬ ν•¨μˆ˜μ—μ„œλŠ” λ‹€μŒκ³Ό 같은 이름이 주둜 μ‚¬μš©λœλ‹€.

  • from: νŒŒλΌλ―Έν„°λ₯Ό ν•˜λ‚˜ λ°›κ³  같은 νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€ ν•˜λ‚˜λ₯Ό λ¦¬ν„΄ν•˜λŠ” λ³€ν™˜ ν•¨μˆ˜

    val date: Date = Date.from(instant)
    
  • of: νŒŒλΌλ―Έν„°λ₯Ό μ—¬λ €κ°œ λ°›κ³ , 이λ₯Ό ν†΅ν•©ν•΄μ„œ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄ μ£ΌλŠ” ν•¨μˆ˜

    val faceCards = Set<Rank> = EnumSet.of(JACK, QUEEN, KING)
    
  • valueOf: from λ˜λŠ” of와 λΉ„μŠ·ν•œ κΈ°λŠ₯을 ν•˜λ©΄μ„œλ„, 의미λ₯Ό 쑰금 더 μ‰½κ²Œ μ½μ„μˆ˜ 있게 이름을 뢙인 ν•¨μˆ˜

    val prime: BigInteger = BigInteger.valueOf(Integer.MAX_VALUE)
    
  • instance/getInstance: μ‹±κΈ€ν„΄μœΌλ‘œ ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό λ¦¬ν„΄ν•˜λŠ” ν•¨μˆ˜

    val luke: StackWalker = StackWalker.getInstance(options)
    
  • createdInstance/newInstance: instance/getInstance 처럼 λ™μž‘ ν•˜μ§€λ§Œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€μ–΄μ„œ 리턴

    val newArray = Array.newInstance(classObject, arrayLen)
    
  • getType: instance/getInstance 처럼 λ™μž‘ν•˜μ§€λ§Œ νŒ©ν† λ¦¬ ν•¨μˆ˜κ°€ λ‹€λ₯Έ ν΄λž˜μŠ€μ— μžˆμ„λŒ€ μ‚¬μš©ν•˜λ©°, νƒ€μž…μ€ νŒ©ν† λ¦¬ ν•¨μˆ˜μ—μ„œ λ¦¬ν„΄ν•˜λŠ” νƒ€μž…

    val fs: FileStore = Files.getFileStore(path)
    
  • newType: createdInstance/newInstance 처럼 λ™μž‘ν•˜μ§€λ§Œ νŒ©ν† λ¦¬ ν•¨μˆ˜κ°€ λ‹€λ₯Έ ν΄λž˜μŠ€μ— μžˆμ„λŒ€ μ‚¬μš©ν•˜λ©°, νƒ€μž…μ€ νŒ©ν† λ¦¬ ν•¨μˆ˜μ—μ„œ λ¦¬ν„΄ν•˜λŠ” νƒ€μž…

    val br: BufferedReader = Files.newBufferedReader(path)
    

κ²½ν—˜μ΄ μ—†λŠ” μ½”ν‹€λ¦° κ°œλ°œμžλ“€μ€ companion 객체 멀버λ₯Ό λ‹¨μˆœν•œ 정적 λ©€λ²„μ²˜λŸΌ λ‹€λ£¨λŠ” κ²½μš°κ°€ λ§Žλ‹€.

ν•˜μ§€λ§Œ companion κ°μ²΄λŠ” 더 λ§Žμ€ κΈ°λŠ₯을 가지고 있으며, companion κ°μ²΄λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•  수 있고, 클래슀λ₯Ό 상속받을 μˆ˜λ„ μžˆλ‹€.

예λ₯Ό λ“€μ–΄ μ½”ν‹€λ¦°μ˜ 코루틴 λ‚΄λΆ€μ—μ„œλŠ” 코루틴 μ»¨ν…μŠ€νŠΈμ˜ companion 객체듀이 CoroutineContext.Key μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.

public interface ContinuationInterceptor : CoroutineContext.Element {
    /**
     * The key that defines *the* context interceptor.
     */
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
}

ν™•μž₯ νŒ©ν† λ¦¬ ν•¨μˆ˜

이미 companion 객체가 μ‘΄μž¬ν•  λ•Œ, 이 companion 객체λ₯Ό 직접 μˆ˜μ •ν•  μˆ˜λŠ” μ—†κ³ , λ‹€λ₯Έ νŒŒμΌμ— ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•  λ•Œ μ‚¬μš©ν•œλ‹€.

λ‹€μŒκ³Ό 같이 Tool μΈν„°νŽ˜μ΄μŠ€λ₯Ό ꡐ체할 수 μ—†λ‹€κ³  κ°€μ •ν•΄λ³΄μž

interface Tool {
  companion object { /*...*/ }
}

companion 객체λ₯Ό ν™œμš©ν•΄μ„œ ν™•μž₯ ν•¨μˆ˜λ₯Ό μ •μ˜ν•  수 μžˆλ‹€.

fun Tool.Companion.createBigTool(/*...*/): BigTool {
  //...
}

// μ‚¬μš©
Tool.createBigTool()

ν†±λ ˆλ²¨ νŒ©ν† λ¦¬ ν•¨μˆ˜

객체λ₯Ό λ§Œλ“œλŠ” 방법 쀑 ν•˜λ‚˜λ‘œ listOf, setOf, mapOf λ“±κ³Ό 같은 ν†±λ ˆλ²¨ νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜λŠ” 방법이 μžˆλ‹€.

ν†±λ ˆλ²¨ ν•¨μˆ˜λŠ” ꡉμž₯히 κ΄‘λ²”μœ„ν•˜κ²Œ μ‚¬μš©ν•˜μ§€λ§Œ public ν†±λ ˆλ²¨ ν•¨μˆ˜λŠ” λͺ¨λ“  κ³³μ—μ„œ μ‚¬μš©ν•  수 μžˆμœΌλ―€λ‘œ IDE의 νŒμ„ λ³΅μž‘ν•˜κ²Œ λ§Œλ“œλŠ” 단점이 μžˆλ‹€.

λ”°λΌμ„œ ν†±λ ˆλ²¨ ν•¨μˆ˜λ₯Ό λ§Œλ“€ λ•ŒλŠ” 이름을 μ‹ μ€‘ν•˜κ²Œ 잘 지정해야 ν•œλ‹€.

κ°€μ§œ μƒμ„±μž

일반적인 μ½”ν‹€λ¦°μ˜ μƒμ„±μžλŠ” ν†±λ ˆλ²¨ ν•¨μˆ˜μ™€ 같은 ν˜•νƒœλ‘œ μ‚¬μš©λœλ‹€.

class A
val a = A()

일반적인 κ΄€μ μ—μ„œ λŒ€λ¬Έμžλ‘œ μ‹œμž‘ν•˜λŠ”μ§€ μ•„λ‹Œμ§€λŠ” μƒμ„±μžμ™€ ν•¨μˆ˜λ₯Ό κ΅¬λΆ„ν•˜λŠ” 기쀀이 λ˜κΈ°λ„ ν•œλ‹€.

ν•¨μˆ˜λ„ λŒ€λ¬Έμžλ‘œ μ‹œμž‘λ  수 μžˆμ§€λ§Œ, μ΄λŠ” νŠΉμˆ˜ν•œ λ‹€λ₯Έ μš©λ„λ‘œ μ‚¬μš©λœλ‹€.

public inline fun <T> List(
  size: Int,
  init: (index: Int) -> T
): List<T> = MutableList(size, init)
public inline fun <T> MutableList(
  size: Int,
  init: (index: Int) -> T
): MutableList<T> {
  val list = ArrayList<T>(size)
  repeat(size) { index -> list.add(init(index))}
  return list
}

μœ„μ™€ 같은 ν†±λ ˆλ²¨ ν•¨μˆ˜λŠ” μƒμ„±μžμ²˜λŸΌ 보이고, μƒμ„±μžμ²˜λŸΌ λ™μž‘λœλ‹€.

ν•˜μ§€λ§Œ νŒ©ν† λ¦¬ ν•¨μˆ˜μ™€ 같은 λͺ¨λ“  μž₯점을 κ°–λŠ”λ‹€.

λ§Žμ€ μ½”ν‹€λ¦° κ°œλ°œμžκ°€ 이λ₯Ό ν†±λ ˆλ²¨ ν•¨μˆ˜μΈμ§€ 잘 λͺ°λ₯΄λ©°, 이λ₯Ό κ°€μ§œ μƒμ„±μž(fake constructor) 라고 λΆ€λ₯Έλ‹€.

κ°€μ§œ μƒμ„±μžλ₯Ό λ§Œλ“œλŠ” μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • μΈν„°νŽ˜μ΄μŠ€λ₯Ό μœ„ν•œ μƒμ„±μžλ₯Ό λ§Œλ“€κ³  μ‹Άμ„λ•Œ
  • refried νƒ€μž… μ•„κ·œλ¨ΌνŠΈλ₯Ό μž‘κ²Œν•˜κ³  μ‹Άμ„λ•Œ

κ°€μ§œ μƒμ„±μžλŠ” μ§„μ§œ μƒμ„±μžμ²˜λŸΌ λ™μž‘λ˜μ–΄μ•Ό ν•˜λ©° μ§„μ§œ μƒμ„±μžμ™€ 같은 λ™μž‘μ„ ν•΄μ•Ό ν•œλ‹€.

κ°€μ§œ μƒμ„±μžλ₯Ό μ„ μ–Έν•˜λŠ” 또 λ‹€λ₯Έ 방법은 invoke μ—°μ‚°μžλ₯Ό κ°–λŠ” companion 객체λ₯Ό μ‚¬μš©ν•˜λ©΄ λΉ„μŠ·ν•œ κ²°κ³Όλ₯Ό 얻을 수 μžˆλ‹€.

class Tree<T> {
  companion object {
    operator fun <T> invoke(size: Int, generator: (Int) -> T): Tree<T> {
      //...
    }
  }
}

// μ‚¬μš©
Tree(10) { "$it" }

이와 같은 방식은 거의 μ‚¬μš©λ˜μ§€ μ•ŠμœΌλ©° ꢌμž₯ν•˜μ§€ μ•ŠλŠ”λ‹€.

νŒ©ν† λ¦¬ 클래슀의 λ©”μ„œλ“œ

νŒ©ν† λ¦¬ ν΄λž˜μŠ€μ™€ κ΄€λ ¨λœ 좔상 νŒ©ν† λ¦¬, ν”„λ‘œν† νƒ€μž… λ“±μ˜ μˆ˜λ§Žμ€ 생성 νŒ¨ν„΄μ΄ μžˆλ‹€.

이런 νŒ¨ν„΄ 쀑 μΌλΆ€λŠ” μ½”ν‹€λ¦°μ—μ„œλŠ” μ ν•©ν•˜μ§€ μ•ŠμœΌλ©°, 일뢀 νŒ¨ν„΄(점측적 μƒμ„±μž νŒ¨ν„΄, λΉŒλ” νŒ¨ν„΄)은 μ½”ν‹€λ¦°μ—μ„œλŠ” μ˜λ―Έκ°€ μ—†λ‹€.

data class Student(
  val id: Int,
  val name: String,
  val surname: String
)

class StudentsFactory {
  var nextId = 0
  fun next(name: String, surname: String) = Student(nextId++, name, surname)
}

val factory = StudentsFactory()
val s1 = factory.next("Marcin", "Moskala")
println(s1) // Student(id=0, name=Marcin, Surname=Moskala)
val s2 = factory.next("Igor", "Wojda")
println(s2) // Student(id=1, name=Igor, Surname=Wojda)

νŒ©ν† λ¦¬ ν΄λž˜μŠ€λŠ” ν”„λ‘œνΌν‹°λ₯Ό κ°€μ§ˆ 수 있고, 이λ₯Ό ν™œμš©ν•˜λ©΄ λ‹€μ–‘ν•œ μ’…λ₯˜λ‘œ μ΅œμ ν™” ν•˜κ±°λ‚˜ κΈ°λŠ₯을 λ„μž…ν•  수 μžˆλ‹€.

정리

코틀린은 νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό λ§Œλ“€ 수 μžˆλŠ” λ‹€μ–‘ν•œ 방법듀을 μ œκ³΅ν•˜λ©° 각각의 방법듀은 μ—¬λŸ¬ νŠΉμ§•μ„ κ°–κ³  μžˆλ‹€.

객체λ₯Ό μƒμ„±ν• λ•ŒλŠ” 이런 νŠΉμ§•μ„ 잘 νŒŒμ•…ν•˜κ³  μ‚¬μš©ν•΄μ•Ό ν•˜λ©°, νŒ©ν† λ¦¬ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” κ°€μž₯ 일반적인 방법은 companion 객체λ₯Ό ν™œμš©ν•˜λŠ” 것이닀.

Item 34 κΈ°λ³Έ μƒμ„±μžμ— 이름 μžˆλŠ” μ˜΅μ…˜ μ•„κ·œλ¨ΌνŠΈλ₯Ό μ‚¬μš©ν•˜λΌ

객체λ₯Ό μ •μ˜ν•˜κ³  μƒμ„±ν•˜λŠ” 방법을 지정할 λ•Œ μ‚¬μš©ν•˜λŠ” κ°€μž₯ 기본적인 방법은 κΈ°λ³Έ μƒμ„±μž(primary constructor)λ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

class User(var name: String, var surname: String)
val user = User("Blue", "Berry")

κΈ°λ³Έ μƒμ„±μžλ‘œ 객체λ₯Ό λ§Œλ“€ λ•ŒλŠ” 객체의 초기 μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ•„κ·œλ¨ΌνŠΈλ“€μ„ μ „λ‹¬ν•œλ‹€.

data class Student(
  val name: String,
  val surname: String,
  val age: Int
)

λ‹€μŒ μ˜ˆλŠ” μΈλ±μŠ€κ°€ λΆ™μ–΄ μžˆλŠ” 글을 좜λ ₯ν•˜λŠ” ν”„λ ˆμ  ν„° 객체이닀.

이 κ°μ²΄λŠ” κΈ°λ³Έ μƒμ„±μžλ₯Ό μ‚¬μš©ν•΄μ„œ 쒅속성을 μ£Όμž…ν•  수 μžˆλ‹€.

class QuotationPresenter(
  private val view: QuotationView,
  private val repo: QuotationRepository
) {
  private var nextQuoteId = -1

  fun onStart() {
    onNext()
  }

  fun onNext() {
    nextQuoteId = (nextQuoteId + 1) % repo.quotesNumber
    val quote = repo.getQuote(nextQuoteId)
    view.showQuote(quote)
  }
}

QuotationPresenter λŠ” κΈ°λ³Έ μƒμ„±μžμ— μ„ μ–Έλ˜μ–΄ μžˆλŠ” ν”„λ‘œνΌν‹°λ³΄λ‹€ 더 λ§Žμ€ ν”„λ‘œνΌν‹°λ₯Ό κ°–κ³  μžˆλ‹€.

μœ„ μ½”λ“œμ—μ„œ nextQuoteId ν”„λ‘œνΌν‹°λŠ” 항상 -1 둜 μ΄ˆκΈ°ν™” λœλ‹€.

κΈ°λ³Έ μƒμ„±μžκ°€ 쒋은 이유λ₯Ό μ„€λͺ…ν•˜κΈ° μœ„ν•΄ λ‹€μŒ 두기지 μ˜ˆμ‹œλ₯Ό μ‚΄νŽ΄λ³Έλ‹€.

점증적 μƒμ„±μž νŒ¨ν„΄

점증적 μƒμ„±μž νŒ¨ν„΄μ€ μ—¬λŸ¬κ°€μ§€ μ’…λ₯˜μ˜ μƒμ„±μžλ₯Ό μ‚¬μš©ν•˜λŠ” ꡉμž₯히 κ°„λ‹¨ν•œ νŒ¨ν„΄μ΄λ‹€.

class Pizza {
    val size: String
    val cheese: Int
    val olives: Int
    val bacon: Int
​
    constructor(size: String, cheess: Int, olives: Int, bacon: Int) {
        this.size = size
        this.cheess = cheess
        this.olives = olives
        this.bacon = bacon
    }
    constructor(size: String, cheese: Int, olives: Int): this(size, cheese, olives, 0)
    constructor(size: String, cheese: Int): this(size, cheese, 0)
    constructor(size: String): this(size, 0)
}

μœ„ μ½”λ“œλŠ” 쒋은 μ½”λ“œκ°€ μ•„λ‹ˆλ‹€.

μ½”ν‹€λ¦°μ—μ„œλŠ” 일반적으둜 μ•„λž˜μ™€ 같이 λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈ(default argument)λ₯Ό μ‚¬μš©ν•œλ‹€.

class Pizza(
    val size: String,
    val cheese: Int = 0,
    val olives: Int = 0,
    val bacon: Int = 0
)

μœ„μ™€ 같은 λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈλŠ” μ½”λ“œλ₯Ό λ‹¨μˆœν•˜κ³  κΉ”λ”ν•˜κ²Œ λ§Œλ“€μ–΄μ€„ 뿐만 μ•„λ‹ˆλΌ λ‹€μ–‘ν•œ κΈ°λŠ₯도 μ œκ³΅ν•œλ‹€.

  • νŒŒλΌλ―Έν„°λ“€μ˜ 값을 μ›ν•˜λŠ” λŒ€λ‘œ 지정할 수 μžˆλ‹€.
  • μ•„κ·œλ¨ΌνŠΈλ₯Ό μ›ν•˜λŠ” μˆœμ„œλ‘œ 지정할 수 μžˆλ‹€.
  • λͺ…μ‹œμ μœΌλ‘œ 이름을 λΆ™μ—¬μ„œ μ•„κ·œλ¨ΌνŠΈλ₯Ό μ§€μ •ν•˜λ―€λ‘œ μ˜λ―Έκ°€ 훨씬 λͺ…ν™•ν•˜λ‹€.

이름 μžˆλŠ” μ•„κ·œλ¨ΌνŠΈ(named argument)λ₯Ό ν™œμš©ν•˜μ—¬ λͺ…μ‹œμ μœΌλ‘œ 이름을 λΆ™μ—¬μ£Όμ–΄ μ˜λ―Έκ°€ λͺ…확해진닀.

λΉŒλ” νŒ¨ν„΄

μžλ°”μ—μ„œλŠ” 이름 μžˆλŠ” μ•„κ·œλ¨ΌνŠΈμ™€ λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈλ₯Ό μ‚¬μš©ν•  수 μ—†μ§€λ§Œ λΉŒλ” νŒ¨ν„΄μ„ μ‚¬μš©ν•œλ‹€.

μžλ°”μ˜ λΉŒλ” νŒ¨ν„΄μ€ μ•„λž˜μ™€ 같은 μž₯점을 μ œκ³΅ν•œλ‹€.

  • νŒŒλΌλ―Έν„°μ— 이름을 뢙일 수 μžˆλ‹€.
  • νŒŒλΌλ―Έν„°λ₯Ό μ›ν•˜λŠ” μˆœμ„œλŒ€λ‘œ 지정할 수 μžˆλ‹€.
  • λ””ν΄νŠΈ 값을 지정할 수 μžˆλ‹€.

λΉŒλ” νŒ¨ν„΄μ„ μ½”ν‹€λ¦°μœΌλ‘œ λ§Œλ“€μ–΄λ³΄λ©΄ λ‹€μŒκ³Ό 같이 μž‘μ„±ν•  수 μžˆλ‹€.

class Pizza private constructor(
    val size: String,
    val cheese: Int,
    val olives: Int,
    val bacon: Int
) {
    class Builder(private val size: String) {
        private var cheese: Int = 0
        private var olives: Int = 0
        private var bacon: Int = 0
        
        fun setCheese(value: Int): Builder = apply {
            cheese = value
        }
        
        fun setOlives(value: Int): Builder = apply {
            olives = value
        }

        fun setBacon(value: Int): Builder = apply {
            bacon = value
        }
        
        fun build() = Pizza(size, cheese, olives, bacon)
    }
}

λΉŒλ” νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈλ₯Ό μ‚¬μš©ν•˜λŠ”κ²ƒκ³Ό 같은 효과λ₯Ό λ³Ό 수 μžˆλ‹€.

ν•˜μ§€λ§Œ λΉŒλ”νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λŠ”κ²ƒ 보닀 이름 μžˆλŠ” μ•„κ·œλ¨ΌνŠΈλ₯Ό μ‚¬μš©ν•˜λŠ”κ²ƒμ΄ 더 쒋은 μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • 더 짧닀
  • 더 λͺ…ν™•ν•˜λ‹€
  • 더 μ‚¬μš©ν•˜κΈ° 쉽닀
  • λ™μ‹œμ„±κ³Ό κ΄€λ ¨λœ λ¬Έμ œκ°€ μ—†λ‹€
    • μ½”ν‹€λ¦°μ˜ ν•¨μˆ˜ νŒŒλΌλ―Έν„°λŠ” 항상 immutable μ΄μ§€λ§Œ λŒ€λΆ€λΆ„μ˜ λΉŒλ” νŒ¨ν„΄μ€ mutable 이닀
    • λ”°λΌμ„œ λΉŒλ” νŒ¨ν„΄μ˜ ν•¨μˆ˜λ₯Ό thread-safe ν•˜κ²Œ κ΅¬ν˜„ν•˜λŠ” 것은 κΉŒλ‹€λ‘œμš΄ 일이닀

λ‹€λ§Œ λΉŒλ” νŒ¨ν„΄μ΄ 더 μœ μš©ν•œ κ²½μš°λ„ μžˆλ‹€.

  • κ°’μ˜ 의미λ₯Ό λ¬Άμ–΄μ„œ 지정해야 ν•˜λŠ” 경우

    val dialog = AlertDialog.Builder(context)
      .setMessage(R.string.fire_missiles)
      .setPositiveButton(R.string.fire, { d, id ->
        // 미사일 λ°œμ‚¬!
      })
      .setNegativeButton(R.string.cancel, { d, id -> 
        // μ‚¬μš©μžκ°€ λŒ€ν™”μƒμžμ—μ„œ μ·¨μ†Œλ₯Ό λˆ„λ₯Έ 경우
      })
      .create()
    
  • νŠΉμ • 값을 λˆ„μ ν˜•νƒœλ‘œ μ‚¬μš©ν•΄μ•Ό ν•œλŠ” 경우

    val router = Router.Builder()
        .addRoute(path = "/home", ::showHome)
        .addRoute(path = "/users", ::showUsers)
        .build()
    

μœ„μ™€ 같은 κ²½μš°μ— λΉŒλ”νŒ¨ν„΄μ„ λ§Œλ“€μ§€ μ•Šκ³  좔가적인 νƒ€μž…μ„ λ§Œλ“€κ³  ν™œμš©ν•˜λ©΄ 였히렀 더 λ³΅μž‘ν•΄μ§ˆμˆ˜μžˆλ‹€.

ν•˜μ§€λ§Œ 이런 νŠΉμˆ˜ν•œ 경우라면 DSL(Domain Specific Language) λΉŒλ”λ₯Ό μ‚¬μš©ν•΄μ„œ κ΅¬ν˜„ν•˜κΈ°λ„ ν•œλ‹€.

정리

일반적인 ν”„λ‘œμ νŠΈμ—μ„œ κΈ°λ³Έ μƒμ„±μžλ₯Ό μ‚¬μš©ν•΄ 객체λ₯Ό λ§Œλ“ λ‹€.

μ½”ν‹€λ¦°μ—μ„œλŠ” 점측적 μƒμ„±μž νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

λŒ€μ‹  λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈλ₯Ό ν™œμš©ν•˜μ—¬ μ‚¬μš©ν•˜λŠ”κ²ƒμ΄ 더 μ’‹λ‹€.

λ””ν΄νŠΈ μ•„κ·œλ¨ΌνŠΈλŠ” 더 짧고, 더 λͺ…ν™•ν•˜κ³ , 더 μ‚¬μš©ν•˜κΈ° 쉽닀.

Item 35 λ³΅μž‘ν•œ 객체λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•œ DSL을 μ •μ˜ν•˜λΌ

코틀린을 ν™œμš©ν•˜λ©΄ DSL(Domain Specific Language)을 직접 λ§Œλ“€ 수 μžˆλ‹€.

μ΄λŠ” λ³΅μž‘ν•œ 객체, 계측 ꡬ쑰λ₯Ό κ°–κ³  μžˆλŠ” 객체듀을 μ •μ˜ν•  λ•Œ ꡉμž₯히 μœ μš©ν•˜λ‹€.

DSL 을 λ§Œλ“œλŠ” 일은 νž˜λ“ μΌμ΄μ§€λ§Œ, ν•œ 번 λ§Œλ“€κ³  λ‚˜λ©΄ λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ(boilerplate)와 λ³΅μž‘μ„±μ„ μˆ¨κΈ°λ©΄μ„œ 개발자의 μ˜λ„λ₯Ό λͺ…ν™•ν•˜κ²Œ ν‘œν˜„ κ°€λŠ₯ν•˜λ‹€.

μ‚¬μš©μž μ •μ˜ DSL λ§Œλ“€κΈ°

μ‚¬μš©μž μ •μ˜ DSL을 λ§Œλ“œλŠ” 방법을 μ΄ν•΄ν•˜λ €λ©΄, λ¦¬μ‹œλ²„λ₯Ό μ‚¬μš©ν•˜λŠ” ν•¨μˆ˜ νƒ€μž…μ— λŒ€ν•œ κ°œλ…μ„ 이해해야 ν•œλ‹€.

μ±… μ°Έκ³ 

μ–Έμ œ μ‚¬μš©ν•΄μ•Ό ν• κΉŒ?

DSL 은 정보λ₯Ό μ •μ˜ν•˜λŠ” 방법을 μ œκ³΅ν•œλ‹€.

  • λ³΅μž‘ν•œ 자료 ꡬ쑰
  • 계측적인 ꡬ쑰
  • κ±°λŒ€ν•œ μ–‘μ˜ 데이터

많이 μ‚¬μš©λ˜λŠ” λ°˜λ³΅λ˜λŠ” μ½”λ“œκ°€ 있고, 이λ₯Ό κ°„λ‹¨ν•˜κ²Œ λ§Œλ“€ 수 μžˆλŠ” λ³„λ„μ˜ μ½”ν‹€λ¦° κΈ°λŠ₯μ΄λ‚˜ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ μ—†λ‹€λ©΄ DSL μ‚¬μš©μ„ 고렀해보아도 쒋을것 κ°™λ‹€.

정리

DSL은 μ–Έμ–΄ λ‚΄λΆ€μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” νŠΉλ²Όν•œ 언어이닀.

λ³΅μž‘ν•œ κ°μ²΄λŠ” λ¬Όλ‘  HTML μ½”λ“œ, λ³΅μž‘ν•œ μ„€μ • λ“±μ˜ 계측ꡬ쑰λ₯Ό κ°–λŠ” 객체λ₯Ό κ°„λ‹¨ν•˜κ²Œ ν‘œν˜„ν• μˆ˜ 있게 ν•΄μ€€λ‹€.

잘 μ •μ˜λœ DSL은 ν”„λ‘œμ νŠΈμ— ꡉμž₯히 큰 도움을 μ£Όμ§€λ§Œ, 쒋은 DSL을 λ§Œλ“œλŠ” μž‘μ—…μ€ ꡉμž₯히 힘이 λ“ λ‹€.