CLOVIS
03/06/2025, 3:55 PMinterface FooA {
fun foo(name: String, value: Int)
}
interface FooB {
fun foo(value: Int)
}
The two interfaces represent two different ways of interacting with a single entity. In some contexts, the library provides an instance of the first interface, and in some contexts, the library provides an instance of the second interface. However, since they are both used to interact with a single entity, it's more convenient for the library author to implement a single instance that can be viewed as both:
private class FooImpl : FooA, FooB {
override fun foo(name: String, value: Int) {}
override fun foo(value: Int) {}
}
So far, everything is good I think.
I think however there is a risk that a user would use a cast to change their access context even if it's not allowed.
withFooA { // provided by the library
it as FooB // will succeed because FooImpl does implement FooB
it.foo(4) // illegal call to FooB.foo in the context of FooA, but will compile and run
}
Should this be considered a risk? Should I split the implementations of the two interfaces to stop this usage? Or should I consider that this is bad code anyway and I should ignore the fact that users could write it?ephemient
03/06/2025, 4:00 PMval foo = FooImpl()
val fooA = object : FooA by foo
val fooB = object : FooB by foo
CLOVIS
03/06/2025, 4:04 PMephemient
03/06/2025, 4:04 PMFooAImpl
and FooBImpl
classes though. if you really need to hold them together, you can do so with
internal class FooImpl(val fooA: FooA, val fooB: FooB) : FooA by fooA, FooB by fooB
and then pull out the individual fooA
, fooB
when passing to users outside your libraryCLOVIS
03/06/2025, 4:06 PMFooB
can be trivially implemented from the function of the same name in FooA
, so it's more convenient for me to co-locate themCLOVIS
03/06/2025, 4:07 PMJoffrey
03/06/2025, 6:49 PM