생성자 대신 팩토리 함수를 사용하라
팩토리 함수는 다음과 같은 장점이 존재한다.
팩토리 함수 내부에서는 생성자를 사용해야 한다. 팩토리 함수는 기본 생성자가 아닌 추가적인 생성자와 경쟁관계이다.
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>? {
// ...
}
}
}
명명 규칙은 자바에서 온 것과 같다.
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"}
가짜 생성자를 만드는 이유는 다음과 같다.
가짜 생성자는 톱레벨 함수를 사용하는 것이 좋다.
팩토리 클래스는 클래스의 상태를 가질 수 있다는 특징 때문에 팩토리 함수보다 다양한 기능을 갖는다.
다음은 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 ~~
캐싱을 활용하거나, 이전에 만든 객체를 복제해서 객체를 생성하는 방법으로 객체 생성 속도를 높일 수 있다.