I'm trying to get my head around when one would ac...
# getting-started
j
I'm trying to get my head around when one would actually use interface delegation ...
Copy code
interface UserRepo {
    sealed interface ListUsersResult {
        ...
    }
    fun listUsers(): ListUsersResult
}
Copy code
class UserRepoPostgreSQL(val connection: Connection): UserRepo {
    fun listUsers(): ListUsersResult {
        // code that queries postgresql
    }
}
Copy code
class UserRepoDelegate(val userRepo: UserRepo): UserRepo by userRepo
so the above example, whenever I need a type in a service, I would mark the parameter type as the
UserRepo
interface. when I want to pass an instance of the interface, I will just create a
UserRepoPostgreSQL(connection = ...)
Why would I need to do
doSomething(userRepo = UserRepoDelegate(UserRepoPostgreSQL(connetion = ...)))
For test cases, I would just pass a
FakeUserRepo()
instead of
doSomething(userRepo = UserRepoDelegate(FakeUserRepo()))
I can think of some very advance use-cases for it, but then I could also just be passing the non-delegate instance in 99.99% of the cases. Where does one typically use these interface delegates for? Maybe some real-world use-cases would enlighten me.
h
My typical use case is if I want to add metadata to a map or a list:
Copy code
data class DataMessage(
	val sender: String,
    val category: Category,
    val sendTimestamp: Instant,
    // … lots more fields …
    val contents: Map<String, Value>
) : Map<String, Value> by contents
I can use this just like I would use the "naked" map, but have all the additional fields around when I need them
a
interface delegation is used by Kotlinx Serialization so that a
Map<String, JsonElement>
can represent a JSON object and also be in the sealed hierarchy of JSON elements https://github.com/Kotlin/kotlinx.serialization/blob/f83385276ce63fd8f76ce333a6f43ec095e9a1da/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt#L191-L194
j
serialization sounds like a very niche use-case, but it's certainly useful The only real-world use-case I could find so far where you could use it repeatedly seem to be services that requires "Traits" or composite classes that combine different behavior
Copy code
class Document(
    private val printer: Printable,
    private val storer: Storable
) : Printable by printer, Storable by storer
Copy code
Document(printer = pdfPrinter, storer = s3)
or inside a test,
Copy code
Document(printer = fakePrinter, storer = fakeStorage)
Thanks for the input! I'll keep on digging and see if there's other cases where this pattern is useful
j
I attempted the composition pattern on a small project last week and ended up building factories for a lot of things and also added the factory to the Delegate's companion object which is pretty close to what you did in your blog article. The builder pattern might even be cleaner here. In my small project, it felt a bit like overkill which is the reason for my post, but I can clearly see the benefits in this article where the domain is slightly more complex. Thanks @Arjan van Wieringen !
j
Interface delegation is also really useful for the Decorator pattern, it allows you to easily decorate an interface implementation and override the methods you want.