How would you choose between the following two sty...
# codingconventions
t
How would you choose between the following two styles, factory functions ("from" functions) and extension functions ("to" functions)?
Copy code
fun main() {
    val a = A(1)
    val b = B(2)
    
    println(b.toA()) //A(a=2)
    println(A.fromB(b)) //A(a=2)
}

data class B(val b: Int)
data class A(val a: Int){
    //1. "static converter" function
    companion object{
        fun fromB(b: B): A = A(b.b)
    }
}

//2. extension function
fun B.toA() = A(this.b)
Asking because I just realize that I have been doing both styles by feeling, arbitrarily. I have no framework in my mind for deciding which style to use and I would like to see if anyone have thought about this before, thanks!
j
I usually prefer extension functions because they can be chained. However, when the type that would be the receiver of the extension is very widespread (e.g.
String
), it might be better to avoid auto-completion pollution and use a factory function. That being said, this is not a hard rule for me, and I pretty much choose by "feeling" as well. I'd be interested in knowing what other people do.
👀 1
👍 1
t
Come to think of it one advantage with the "from" functions is that it aligns with the "static instance" style pretty well like for example
Setting.from(choice)
is similar in style with
Setting.DEFAULT
Setting.OPTION_A
Setting.OPTION_B
etc
j
Yep, I guess that's another reason why I usually use a factory function to "parse" a string into an enum value.
r
I prefer extension functions, source -> target as left to right reads more naturally to me, and more importantly because they can be chained they can be null dereferenced -
b?.toA()
is so much nicer than
if (b != null) A.from(b) else null
👍 4
p
If you go down the factory function route for the reasons above, you can still always do
b?.let(A::from)
r
for me it depends a lot on what the natural understanding of the classes is, say you have a domain:
data class Foo
and some DTO wrapper, i.e. for serializing json:
data class FooDto
I'd ask if the conversion belongs naturally to the object, i.e. if I want to convert from the domain object having
Foo(...).toDto()
I'd ask if it makes sense domain wise for
Foo
to know about serialization (in my experience the answer here is almost always no). If the answer is no, I'd go with extension function living at the DTO. If yes then companion object Similar on the DTO, here my experience is that the DTO is almost always a natural wrapper for a/some specific domain object(s) and thus often go with the companion object personally I think that there's currently an overuse of extension methods, having a mess of imports cluttering up the code, for the sake of abstractions that are almost always included anyway and feels like a natural part of the domain in question because of that feeling of overuse, I mostly go with modelling on the domain first if in doubt. And after all it's fairly easy to go from domain to extension if it turns out that it's the more natural solution
👍 2