본문 바로가기
📚 Kotlin

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

by GroovyArea 2023. 1. 29.

자바를 공부하며 가장 의미 있게 읽은 책은 [이펙티브 자바]이다. 

자바를 만든 개발자가 직접 집필한 책이므로, 자바를 자바답게 사용하는 방법이 아주 명확하게 설명되어 있다.

가장 첫 챕터인 '생성자 대신 팩토리 메소드를 사용하라'라는 아이템을 굉장히 의미 있게 읽게 되었고, 그 내용을 코드를 작성할 때 애용했었다.

 

코틀린도 자바의 이점을 그대로 가져간 언어이므로, 비슷하게 적용 되는 부분이 있을지를 생각해 봤다.

회사에 들어가서 코틀린을 배우고 코드를 작성하면서, 가장 눈에 띄었던 부분이 dto 객체를 매핑하는 부분이었고, 팩토리 메서드를 사용하면 어떨까라는 생각을 했다.

 

코틀린을 사용하는 분들과 구글링을 통해 자바와 동일하게 팩토리 메서드를 많이들 사용한다는 정보를 얻었고, 그대로 적용했었다.

 

이펙티브 코틀린 책을 펼치며 목차를 훑었을 때 이 챕터가 눈에 띄었고, 내가 알던 정보와 비교하며 읽으면 재밌겠다는 생각을 했고 비로소 읽게 되었다. 그 내용을 정리해 본다.

 

팩토리 함수의 이점 

생성자의 역할을 대신한다.

 

생성자와 다르게 함수에 의미 있는 이름을 붙일 수 있다.

가령 객체를 생성하기 위해 필요한 파라미터가 무엇인지 명시를 할 수 있다는 점이다.

 

이를 테 면,

class ArrayList {

	companion object {
    	
        fun withSize(size: Int) : ArrayList {
        	return //...
        }
    }
}

ArrayList 객체를 생성하기 위한 withSize() 팩토리 함수는 사이즈를 파라미터로 받는 객체를 제공한다는 의미를 명확하게 알 수 있다.

 

또한, 함수가 원하는 형태의 타입을 리턴할 수 있다.

가령 listof()라는 함수를 떠올렸을 때, List 인터페이스 타입을 return 한다는 점에서 그 의미를 코틀린 라이브러리가 충족시키고 있다.

 

호출될 때마다 새 객체를 만들 필요가 없다.

싱글턴 객체를 생성한다거나, 캐싱을 이용할 수가 있다.

객체 생성이 불가능할 경우, null을 return 할 수 있다.

이를 테 면, Connection.createOrNull() 등등..

 

존재하지 않는 객체를 return 할 수도 있다.

앞으로 만들어질 객체를 사용하거나 프락시 객체를 사용할 수 있다.

 

객체 외부에 팩토리 함수를 적용할 수 있다.

가시성 조절이 가능한 것이 장점이며, 같은 파일 혹은 같은 모듈에서만 접근하게 지정할 수 있다.

 

팩토리 함수는 생성자를 만들기 위한 복잡한 객체도 만들 수 있다.

또한, 무조건 호출되는 생성자를 원하는 시기에 호출할 수 있다.

 

이처럼 장점이 정말 많다.

코틀린에서 팩토리 함수를 생성하는 방법을 알아보자.

 

Companion 팩토리 함수

코틀린에서 팩토리 함수를 정의하는 가장 일반적인 방법이다.

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

가장 익숙한 방법이다.

 

코틀린에서는 interface에도 companion object를 사용할 수 있어 동일하게 팩토리 함수를 선언할 수 있다.

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(3,4,5,6)

참 혁신적이 고만.

 

팩토리 함수 명 규약

from : 파라미터를 하나 받고, 해당 타입의 인스턴스 하나 return

val date = Date.from(instatnt)

 

of : 파라미터를 여러 개 받고, 통합하여 인스턴스를 return

val cards : Set <Rank> = EnumSet.of(JACK, QUEEN, KING)

 

valueOf : from, of와 비슷함. 의미를 조금 더 쉽게 읽을 수 있는 함수

val prime : BigInteger = BigInteger.valueOf(Integer.MAX_VALUE)

 

instance, getInstance : 싱글턴 인스턴스 하나를 return

val connection = Connection.getInstance(parameter)

 

createInstance, newInstance : getInstance와 동일, 싱글턴이 아님. 호출할 때마다 새로운 인스턴스를 return

val newPerson = Person.newInstance(age, name)

 

getType : getInstance와 동일, 팩토리 함수가 다른 클래스에 있을 때 사용, 타입은 팩토리 함수에서 return 하는 타입

val fs: FileStore = Files.getFileStore(path)

 

newType : newInstance와 동일,

val br : BufferedReader = Files.newBufferedReader(path)

 

 

이와 같이 규약을 지키며 만들면 좋을 것 같다.

나처럼 코틀린 입문한 지 얼마 되지 않은 개발자는 코틀린의 companion object를 static처럼 사용하는 경향이 있는데,

위처럼 c.o도 엄연한 객체이므로 interface를 상속받아 구현할 수 있다.

이를 잘 활용하여 캐싱을 구현하거나 테스트를 위한 가짜 객체를 생성할 수 있을 것이다.

 

코틀린 팀 제품 구현을 자주 참고해서 사용 방법을 명확히 이해해야겠다.

 

 

확장 팩토리 함수

이미 companion 객체가 존재할 때, 이 객체의 함수처럼 사용하는 팩토리 함수를 만들 경우에 사용한다.

확장 함수를 이용해서!

 

interface Tool {

	companion object {
    	//....
    }
}

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

Tool.createBigTool()

이런 식으로 companion 객체를 직접 수정하지 않고, 외부 파일에서 확장 팩토리를 따로 정의해서 사용할 수 있다.

차암나~

물론 확장하려면 확장처에 companion이 정의되어 있어야겠다.

 

 

톱레벨 팩토리 함수

대표적인 예시로, mapof, listof, setof 등이 있다.

 

가령 listof(1,2,3) 이 기존 List.of(1,2,3) 보다 가독성이 훨씬 좋기 때문에, 톱레벨 함수를 사용한다.

하지만, 톱레벨 함수를 만들 때 함수의 이름을 클래스 메서드 이름처럼 짓지 말자.

신중하게 생각해서 이름을 지정하자.

 

 

가짜 생성자

class A

val A = A()

 

코틀린의 생성자는 톱레벨 함수와 같은 형태이다.

 

톱레벨 함수처럼 생성자 함수도 참조할 수 있다.

val reference: () -> A = ::A

 

일반적으로 생성자와 함수를 구분짓는 방법은 대문자 시작 유무이다.

 

하지만,

List(4) ??

 

인터페이스가 어떻게 생성자를 가지지?

 

코틀린이 제공하는 라이브러리에서는 가능하다.

이러한 톱레벨 함수는 생성자처럼 보이고, 생성자처럼 작동한다.

그와 동시에 팩토리 함수의 이점을 갖는다.

이것도 엄연한 팩토리 함수이므로, 가짜 생성자라 불리운다.

 

데게,

인터페이스를 위한 생성자를 만들고 싶을 경우

refied 타입 인자를 갖게 하고 싶을 경우 

생성한다.

 

 

정리

팩토리 함수를 만들 수 있는 다양한 방법들의 특징을 명확히 이해하고 사용하자.

가장 일반적인 방법은 companion object 를 이용하는 방법이다.

자바의 정적 팩토리 메서드 패턴과 유사하고 안전하고 익숙하게 코틀린이 계승했기 때문이다.

 

반응형