Skip to content

Instantly share code, notes, and snippets.

@jayv

jayv/cars.kt Secret

Last active October 13, 2020 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jayv/d387abe704a335a7a416d0811dc1df74 to your computer and use it in GitHub Desktop.
Save jayv/d387abe704a335a7a416d0811dc1df74 to your computer and use it in GitHub Desktop.
Kotlin Composition/Trait Experiment
/**
* 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