https://kotlinlang.org logo
Title
j

james.cruz

12/19/2018, 6:28 AM
Hello, I have a public interface called "Foo" and it has an internal default
object
implementation. What's the preferred way to get the implementation's instance? 1️⃣ Companion object as the default implementation
interface Foo {
  companion object : Foo {
    // default implementation goes here
2️⃣ Quasi constructor
interface Foo {
  companion object {
    operator fun invoke(): Foo = DefaultFooImpl
3️⃣ Top level method
fun defaultFoo(): Foo = DefaultFooImpl
c

Czar

12/19/2018, 10:54 AM
1️⃣ is different to 2️⃣ and 3️⃣ , the former creates a singleton which is tied to Foo interface, whereas latter create new instance on each invocation, so choice here depends on what you want. 1️⃣ is very weird, I don't see any valid justification for it, feel free to change my mind 🙂 Having a singleton default implementation of an interface can be achieved in a much more readable standard way. As for 2️⃣ vs 3️⃣ (you want a new instance on each invocation), I'd say 2️⃣ effectively tries to hide the fact that Foo is an interface and IMHO hinders readability a lot, I probably would not allow it in any code base under my supervision, whereas 3️⃣ is readable and conveys intention clearly.
j

james.cruz

12/19/2018, 11:12 AM
Thanks for the reply! For clarification,
DefaultFooImpl
is an
object
, so they're all returning the same singleton instance every time. Regardless, 3️⃣ still seems to be the better choice, considering what you said
m

Mike

12/19/2018, 1:54 PM
If DefaultFooImpl is an object, why aren’t you just referencing the functions/properties directly on it? Rather than creating an extra level of indirection to get the pointer. Rather than
defaultFoo().someFunction()
, you can just do
DefaultFooImpl.someFunction()
Or you want to be able to change the value of defaultFoo w/o having to change all occurrences throughout the code? The use-case is unclear to me.
👆 2
j

james.cruz

12/20/2018, 2:18 AM
Hi Mike, That's because DefaultFooImpl is an
internal
object, as others have pointed out. We're exposing an API to 3rd party users of our library. So we want to keep source and binary compatibility as much as possible.
c

Czar

12/20/2018, 7:25 AM
Then I suggest making it as obvious as possible e.g. exposing it as a property instead of a method, and with a descriptive name:
Foo.defaultSingletonImpl
interface Foo {
	companion object {
		val defaultSingletonImpl = DefaultFooImpl
	}
}
2
b

bdawg.io

12/28/2018, 7:24 AM
My vote would be to follow the other conventions of default factories:
fun Foo(): Foo = DefaultFooImpl
(for example,
emptySet()
returns a singleton of
Set<Nothing>
)
This is also similar to
Job
which is an interface but you can get the default implementation using
Job()