-
-
Save jayv/d387abe704a335a7a416d0811dc1df74 to your computer and use it in GitHub Desktop.
Kotlin Composition/Trait Experiment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This is a toy example exploring using composition over inheritance to combine behavior, where reusable components | |
* have some globally shared and specific config. Leveraging kotlin traits with interface delegation, delegated | |
* properties and default values. | |
* The main drawback seems to be constructors lacking the ability to create local references to pass instances to the | |
* superclass constructors and its interface delegates, or am I missing something to make this less boilerplate? | |
*/ | |
import kotlin.math.min | |
import kotlin.properties.ReadOnlyProperty | |
import kotlin.reflect.KProperty | |
abstract class Car(open val engine: Engine, open val entertainment: Entertainment) : | |
Engine by engine, | |
Entertainment by entertainment | |
interface Engine { | |
fun speed(): Int | |
} | |
interface Entertainment { | |
fun volume(): Int | |
} | |
// Global config potentially used by all components/behaviors, imagine config varying by base vs luxury vs sport model | |
class CarConfig(config: Any) { | |
val speedLimit: Int by ConfigValue(config, "global.speedLimit") | |
val soundLimit: Int by ConfigValue(config, "global.soundLimit") | |
} | |
// Config lookup mechanism, implementation is irrelevant, contains global and component specific config properties | |
class ConfigValue<T, R>(config: Any, key: String) : ReadOnlyProperty<T, R> { | |
override fun getValue(thisRef: T, property: KProperty<*>): R = TODO() | |
} | |
class DefaultEngine(config: Any, private val baseConfig: CarConfig) : Engine { | |
private val speed: Int by ConfigValue(config, "engine.speed") // component specific config | |
override fun speed(): Int = min(speed, baseConfig.speedLimit) | |
} | |
class RaceEngine(config: Any, private val baseConfig: CarConfig) : Engine { | |
private val speed: Int by ConfigValue(config, "engine.speed") // component specific config | |
override fun speed(): Int = min(2 * speed, baseConfig.speedLimit) | |
} | |
class DefaultEntertainment(config: Any, private val baseConfig: CarConfig) : Entertainment { | |
private val volume: Int by ConfigValue(config, "entertainment.volume") | |
override fun volume(): Int = min(volume, baseConfig.soundLimit) | |
} | |
// We don't want to do this obviously | |
class SedanWithDuplication(config: Any) : | |
Car(DefaultEngine(config, CarConfig(config)), DefaultEntertainment(config, CarConfig(config))) | |
// We can do this, leveraging inheritance and default values to simulate having "variables" in our constructor | |
class Sedan(config: Any) : SedanBase(config, CarConfig(config)) | |
abstract class SedanBase( | |
config: Any, | |
baseConfig: CarConfig, | |
engine: Engine = DefaultEngine(config, baseConfig), | |
entertainment: Entertainment = DefaultEntertainment(config, baseConfig) | |
) : Car(engine, entertainment) | |
// But this gets more annoying as we'd have to create an extra Base-type for every combination... | |
class RaceCar(config: Any) : RaceCarBase(config, CarConfig(config)) | |
abstract class RaceCarBase( | |
config: Any, | |
baseConfig: CarConfig, | |
engine: Engine = RaceEngine(config, baseConfig), | |
entertainment: Entertainment = DefaultEntertainment(config, baseConfig) | |
) : Car(engine, entertainment) | |
// And it gets worse with a level of indirection for every layer of dependency, eg. | |
// adding a dependency on Engine from Entertainment: | |
class SportEntertainment(config: Any, baseConfig: CarConfig, private val engine: Engine) : | |
Entertainment by DefaultEntertainment(config, baseConfig) { | |
fun displaySpeed() { | |
println(engine.speed()) | |
} | |
} | |
class FancyRaceCar(config: Any) : FancyRaceCarBase(config, CarConfig(config)) { | |
init { | |
entertainment.displaySpeed() | |
} | |
} | |
abstract class FancyRaceCarBase(config: Any, baseConfig: CarConfig) : | |
FancyRaceCarBaseBase(config, baseConfig, RaceEngine(config, baseConfig)) | |
abstract class FancyRaceCarBaseBase( | |
config: Any, | |
baseConfig: CarConfig, | |
override val engine: RaceEngine, | |
override val entertainment: SportEntertainment = SportEntertainment(config, baseConfig, engine) | |
) : Car(engine, entertainment) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment