Skip to content

Instantly share code, notes, and snippets.

@milgner
Last active December 7, 2020 09:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milgner/b9a4ae95d112a392dfa82d8934527277 to your computer and use it in GitHub Desktop.
Save milgner/b9a4ae95d112a392dfa82d8934527277 to your computer and use it in GitHub Desktop.
An idea for a simple factory in Kotlin
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
interface IFaktory<T: Any> {
fun build() : T
}
// the P generic type doesn't work as of yet
// it is supposed to map a KProperty to a function that returns the same type as that property
fun <T: Any, P> faktory(ctor: KCallable<T>, setters: Map<KProperty<P>, () -> P>) : IFaktory<T> {
return object : IFaktory<T> {
override fun build(): T {
with(ctor) {
val settersByPropertyName = setters.mapKeys { it.key.name }
return callBy(parameters.associate { param ->
param to {
val initializer = settersByPropertyName.get(param.name)
if (initializer != null) {
initializer.invoke()
} else {
if (param.isOptional) {
null
} else {
throw NotImplementedError("Please add ${param.name} to your factory attributes")
}
}
}
})
}
}
}
}
data class Foo(val bar: String, val baz: Int) {}
val myFaktory = faktory(::Foo, mapOf(
Foo::bar to { "bar" },
Foo::baz to { 42 }
))
fun main() {
val instance = myFaktory.build()
println(instance)
}
@thedanielhanke
Copy link

thedanielhanke commented Dec 7, 2020

using primary constructor and filtered parameters.

// list of available ctor params
inline fun <reified T : Any> KClass<T>.getProperties(): List<KProperty<*>> {
    val primaryConstructor = primaryConstructor ?: return emptyList()

    return declaredMemberProperties.filter {
        primaryConstructor.parameters.any { p -> it.name == p.name }
    }
}

// the P generic type doesn't work as of yet
// it is supposed to map a KProperty to a function that returns the same type as that property
inline fun <reified T : Any, P> faktory(
    klass: KClass<T>,
    setters: Map<KProperty<P>, () -> P>
): IFaktory<T> {
    return object : IFaktory<T> {
        override fun build(): T {
            with(klass.primaryConstructor!!) {
                if (setters.isEmpty()) {
                    return callBy(mapOf())
                }

                val props = klass.getProperties()
                val settersByPropertyName = setters
                    .filter { props.contains(it.key) }
                    .mapKeys { it.key.name }

                return callBy(parameters.associateWith { param ->
                    settersByPropertyName.get(param.name)?.invoke()
                })
            }
        }
    }
}



faktory(
    Foo::class,
    mapOf(
        Foo::bar to { "bar" },
        Foo::baz to { 42" }
    )
)
.build()
.also(::println)

@thedanielhanke
Copy link

thedanielhanke commented Dec 7, 2020

merge / override build params:

typealias Props<P> = Map<KProperty<P>, () -> P>

interface IFaktory<T : Any, P> {
    fun build(): T
    fun build(setters: Props<P>): T
    fun buildWith(setters: Props<P>): T
}

inline fun <reified T : Any, P> faktory(
    klass: KClass<T>,
    _setters: Props<P>
): IFaktory<T, P> {
    return object : IFaktory<T, P> {
        override fun build(): T {
            return build(_setters)
        }

        override fun buildWith(setters: Props<P>): T {
            val mergedMap = _setters
                .toMutableMap()
                .apply { setters.forEach { key, value -> 
                    merge(key, value) { _, new -> new }
                } }

            return build(mergedMap)
        }

        override fun build(setters: Props<P>): T {
.....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment