Implementation by delegation is a great feature that allows to use composition over inheritance and cut on boilerplate tremendously. Unfortunately, it is limited by the fact that it only allows primary constructor parameters and in-place created instances as delegates. In a lot of scenarios (if not in most scenarios!) the fact that current implementation is backed by another one should be an implementation detail:
interface Executor<Request, Response> {
suspend fun execute(request: Request): Response
}
class Throttler<Request, Response>(
private val base: Executor<Request, Response>,
private val settings: ThrottlerSettings
) : Executor<Request, Response> {
var requestsPerMinute: Int
get() ...
set() ...
// ...
}
class SmartThrottler<Request, Response>(
private val base: Executor<Request, Response>,
...
) : Executor<String, ByteArray> by throttler {
// these settings are an implementation detail
private val throttlerSettings = ThrottlerSettings(...)
private val throttler = Throttler<String, ByteArray>(base, throttlerSettings)
fun adjustRpm() {
throttler.requestsPerMinute = computeRpm()
}
}