반응형

이 게시물은 다음 링크를 참조하여 학습했습니다.

 

팩토리 메서드 패턴 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

 

아이템 33: 생성자 대신 팩토리 함수를 사용하라

기본적으로 클래스의 인스턴스를 만드는 방법으로 "생성자"가 있다.

하지만 생성자가 인스턴스를 만드는 유일한 방법은 아니다.

 

디자인 패턴으로 다양한 생성 패턴(creational pattern)들이 만들어져 있는데, 이렇게 생성자 역할을 대신 해주는 함수를 팩토리 함수라 한다.

 

팩토리 함수의 장점

  • 생성자와 다르게 이름 붙일 수 있음
  • 생성자와 다르게 원하는 형태의 타입 리턴할 수 있음
  • 생성자와 다르게 싱글턴 패턴을 사용하거나, null을 리턴할 수 있음
  • 생성자와 다르게 아직 존재하지 않는 객체를 리턴할 수 있음( 어노테이션 기반 라이브러리에서 자주 사용)
  • 객체 외부에 만들면 가시성을 원하는 대로 제어할 수 있음
  • 인라인으로 만들 수 있음
  • 생성자로 만들기 복잡한 객체도 만들 수 있음
  • 생성자를 원하는 때에 호출할 수 있음

 

팩토리 함수의 종류

  • Companion 객체 팩토리 함수
  • 확장 팩토리 함수
  • 톱레벨 팩토리 함수
  • 가짜 생성자
  • 팩토리 클래스의 메서드


1. Companion 객체 팩토리 함수
팩토리 함수를 생성 하는 가장 기본적인 방법이다. companion object를 사용한다.

C++같은 프로그래밍 언어에서는 이를 "이름있는 생성자"라고 한다.

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)

 

이는 interface로도 구현 가능하다.

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

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

//사용
val list = MyLinkedList.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)

//createInstance / newInstance : getInstance처럼 동작하지만 싱글턴 x
val newArray = Array.newInstance(classObject, arrayLen)

// getType : getInstance처럼 동작하지만 팩토리 함수가 다른 클래스에 있을 때 사용
val fs: FileStore = Files.getFileStore(path)

// newType : newInstance처럼 동작하지만 팩토리 함수가 다른 클래스에 있을 때 사용
val br: BuffererReader = Files.newBufferReader(path)


2. 확장 팩토리 함수

이미 companion object가 존재할 때, 이 객체의 함수처럼 사용할 수 있는 팩토리 함수를 만들고자 할 때가 있다. 하지만 외부 라이브러리라면 companion object를 직접 수정할 수 없을 것이다....

이럴때는 companion 객체를 확장해서 사용하면 된다!

// 인터페이스 교체 불가능
interface Tool{
	companion object { /* ... */ }
}

// companion 객체 활용 확장함수 정의
fun Tool.Companion.createBigTool( /* ... */ ) : BigTool{
	// ...
}

// 호출
Tool.createBigTool()

단, 적어도 비어있는 companion object 있어야한다.

 

3. 톱레벨 팩토리 함수

대표적으로 listOf, setOf, mapOf … 등이 있다.

광범위하게 사용할 수 있지만, 복잡해질수 있다.
-> 이름을 지을때 신중하게 지어야 함!

 

4. 가짜 생성자

kotlin의 List, MutableList는 interface이기 때문에 생성자를 갖을수 없다.
하지만, 생성자 처럼 쓰인다.
이는 톱레벨 함수로 선언 되어있기 때문 -> 이를 “가짜 생성자”라고 한다.

가짜 생성자를 만드는 이유

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

이러한 점을 제외하고는 가짜 생성자는 진짜 생성자처럼 동작해야 한다.

+invoke 연산자...

 

5. 팩토리 클래스의 메서드
클래스를 만드는 팩토리 클래스를 사용하면 프로퍼티를 가질 수 있고, 다양한 로직을 처리할 수 있다.

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)

-> next를 호출할 때마다 ID값이 +된다.

 

결론

객체를 생성할 때 특성에 맞게 팩토리 함수를 사용하자!

 

아이템 34: 기본 생성자에 이름있는 옵션 아규먼트를 사용하라

 

아이템 35: 복잡한 객체를 생성하기 위한 DSL을 정의하라

 

 

 

반응형

+ Recent posts