[Effective Kotlin] 아이템 33. 생성자 대신 팩토리 함수를 사용하라

Jimin Lim·2023년 8월 27일
0

Effective Kotlin

목록 보기
33/39
post-thumbnail

아이템 33

생성자 대신 팩토리 함수를 사용하라

  • 팩토리 함수: 생성자의 역할을 대신 해주는 함수

팩토리 함수는 다음과 같은 장점이 존재한다.

  1. 함수에 이름을 붙일 수 있다.
    ArrayList.withSize(3)과 같이 인자에 의미를 부여할 수 있다.
  2. 함수가 원하는 형태의 타입을 리턴할 수 있다.
    listOf는 List 인터페이스를 리턴한다. 플랫폼에 따라 실제 반환하는 객체가 다르게 할 수 있다.
  3. 호출될 때마다 새 객체를 만들 필요가 없다.
    싱글턴 패턴처럼 하나만 생성하게 강제하거나 최적화를 위해 캐싱 메커니즘을 사용할 수도 있다. 또한 객체를 만들 수 없다면 null을 리턴하게 할 수 있다.
  4. 아직 존재하지 않는 객체를 리턴할 수 있다.
    어노테이션 처리를 기반으로 하는 라이브러리에서는 팩토리 함수를 많이 사용한다. 이를 활용하면 프로젝트를 빌드하지 않고도 앞으로 만들어질 객체를 사용하거나, 프록시를 통해 만들어지는 객체를 사용할 수 있다.
  5. 객체 외부에 팩토리 함수를 만들면 그 가시성을 원하는 대로 제어할 수 있다.
    톱레벨 팩토리 함수를 같은 파일 또는 같은 모듈에서만 접근하게 만들 수 있다.
  6. 인라인으로 만들 수 있으며 그 파라미터들을 reified로 만들 수 있다.
  7. 생성자는 즉시 슈퍼클래스 또는 기본 생성자를 호출해야 하지만 팩토리 함수는 원하는 때에 생성자를 호출 할 수 있다.

팩토리 함수 내부에서는 생성자를 사용해야 한다. 팩토리 함수는 기본 생성자가 아닌 추가적인 생성자와 경쟁관계이다.

Companion 객체 팩토리 함수

open 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)

자바의 정적 팩토리 함수와 같다. 이 접근 방식을 인터페이스로 나타내면 다음과 같다.

open 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>? {
            // ...
        }
    }
}

명명 규칙은 자바에서 온 것과 같다.

  • from: 파라미터 하나 받고, 같은 타입의 인스턴스 하나 리턴
  • of: 파라미터 여러 개 받고, 이를 통합해서 인스턴스 만들어줌
  • valueOf: from, of 와 비슷한 기능
  • instance or getInstance: 싱글톤으로 인스턴스 하나 리턴
  • createInstance or newInstance: getInstance처럼 동작하지만, 싱글턴이 아님
  • getType: 팩토리 함수가 다른 클래스에 있을 때 사용 (ex. getFileStore())
  • newType: 싱글턴이 아니면서 다른 클래스에 있을 때 사용

companion 객체는 인터페이스를 구현하거나, 클래스를 상속받을 수 있다. 일반적으로 추상클래스를 상속받는 형태로 팩토리 함수를 만든다고 한다..

확장 팩토리 함수

companion 객체를 수정하지 못하고 다른 파일에 함수를 만들어야 할 때, 확장 함수를 만들 수 있다.

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

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

//호출
Tool.createBigTool()

다만 확장하려면 기존 코드에 적어도 빈 컴패니언 객체가 있어야 한다.

톱레벨 팩토리 함수

예를 들어 List.of(1,2,3) 대신 listOf(1,2,3) 을 사용한 예시가 있다.

public 톱레벨 함수는 모든 곳에서 사용할 수 있어, 신중할 필요가 있다.

가짜 생성자

List, MutableList는 인터페이스로 생성자를 가질 수 없다. 하지만 생성자처럼 사용하는 코드를 볼 수 있다.

List(4) {"User$it"}

가짜 생성자를 만드는 이유는 다음과 같다.

  1. 인터페이스를 위한 생성자를 만들고 싶을 때
  2. reified타입 아규먼트를 갖게 하고 싶을 때

가짜 생성자는 톱레벨 함수를 사용하는 것이 좋다.

팩터리 클래스의 메서드

팩토리 클래스는 클래스의 상태를 가질 수 있다는 특징 때문에 팩토리 함수보다 다양한 기능을 갖는다.

다음은 id 값을 가지고 있는 팩터리 클래스를 이용한 예시이다.

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

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

val factory = StudentFactory()
val s1 = factory.next("name1", "surName1")
println(s1) //id=0 ~~
val s2 = factory.next("name2", "surName2")
println(s2) //id=1 ~~ 

캐싱을 활용하거나, 이전에 만든 객체를 복제해서 객체를 생성하는 방법으로 객체 생성 속도를 높일 수 있다.

profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

0개의 댓글