Advancedkotlin 8
Advancedkotlin 8
1/35 2/35
Interfaces Example
We want to manage the NPCs for a game:
interface NPC {
▶ Declare abstract functions and functions implementations var location: Point
fun move()
▶ Can have abstract properties ➩ Cannot store state fun draw()
}
fun updateScreen() {
for (npc in NPCs) {
npc.move()
npc.draw()
}
}
}
3/35 4/35
// ..
val manager = ManageNPCs()
manager.addNPC(Vampire("Dracula", Point(0, 10)))
5/35 6/35
interface StringProcessor {
fun transform(s: String): String
}
of an interface
▶ Used when you need an object
of a slightly different class fun main () {
val duplicator = object : StringProcessor {
and you don’t want to write a new class
override fun transform(s: String): String {
▶ They are written as the object keyword followed by a colon, return s + s
the interface or parent class, and the body }
}
7/35 8/35
Object for Singletons Example
object Identifiers {
private var counter = 0
▶ It allows the definition of attributes accessed through the class companion object {
name, no need to use an instance const val INITIAL_LIFE = 10
▶ Typical use: defining constants, factory methods, etc const val FIGHT_STRENGTH = 20
}
}
11/35 12/35
Generics Example
13/35 14/35
▶ If T is only produced, it is marked as “out T”: ▶ If we have a class C with a type parameter out T:
interface Producer <out T> { • If S is a subtype of B then a value of type C<S> can be
fun produce(): T assigned to a variable of type C<B>
} val b: B = s
val cb : C<B> = cs
▶ If T is only consumed, it is marked as “in T”:
interface Consumer <in T> { ▶ If we have a class C with a type parameter in T:
operator fun consume(value: T): Int • If S is a subtype of B then a value of type C<B> can be
} assigned to a variable of type C<S>
val b: B = s
val cs : C<S> = cb
15/35 16/35
Example Example (2)
open class Animal
class Cat: Animal()
class Dog: Animal() interface Veterinary<in T> {
fun cure(patient: T)
interface PetShop<out T> { }
fun sell(money: Int): T
} fun testVeterinary(catVet: Veterinary<Cat>,
genericVet: Veterinary<Animal>) {
fun testPetShop(catPetShop: PetShop<Cat>, val dVet: Veterinary<Dog> = catVet // Error
genericPetshop: PetShop<Animal>) { val cVet: Veterinary<Cat> = genericVet // OK
val dPetShop: PetShop<Dog> = catPetShop // Error val gVet: Veterinary<Animal> = catVet // Error
val cPetShop: PetShop<Cat> = genericPetshop // Error }
val gPetShop: PetShop<Animal> = catPetShop // OK
}
17/35 18/35
▶ Generic functions are marked with <T> before the class name: ▶ E.g. in the class ManageNPCs we could have written
updateScreen like this:
fun <T> mutableListOf(vararg elements: T): MutableList<T>
▶ When used, the type parameter can be omitted if it is inferred fun updateScreen() { fun updateScreen() {
for (npc in NPCs) { for (npc in NPCs)
from the context:
npc.move() with (npc) {
val list = mutableListOf(1, 2, 3) npc.draw() move()
} draw()
} }
}
19/35 20/35
21/35 22/35
run with
▶ Similar to apply but returns the result of the lambda ▶ Similar to run but the object is received as a parameter
▶ This ▶ This
val currentPosition = vampire.run { val currentPosition = with (vampire) {
move() move()
draw() draw()
position position
} }
▶ is equivalent to ▶ is equivalent to
vampire.move() vampire.move()
vampire.draw() vampire.draw()
val currentPosition = vampire.position val currentPosition = vampire.position
23/35 24/35
also let
▶ Similar to apply but the object is accessed through it
▶ Similar to also but the result is the value of the lambda
▶ This
▶ This
return Vampire("Dracula", Point(2,3)).also {
it.move() val currentPosition = vampire.let {
it.draw() it.move()
println("The name of the vampire is ${it.name}") println("The name of the vampire is ${it.name}")
} it.position
}
▶ is equivalent to
▶ is equivalent to
val vampire = Vampire("Dracula", Point(2,3))
vampire.move() vampire.move()
vampire.draw() println("The name of the vampire is ${vampire.name}")
println("The name of the vampire is ${vampire.name}") val currentPosition = vampire.position
return vampire
25/35 26/35
27/35 28/35
And we add a function for each of the elements that can be added:
We create a class to incrementally build all the parameters:
fun setDayLight(dayLight: Double): ScreenBuilder {
class ScreenBuilder { this.dayLight = dayLight
private var dayLight: Double = 0.0 return this
private var lightSources = ArrayList<LightSource>() }
private var NPCs = ArrayList<NPC>()
Or, using apply:
private var buildings = ArrayList<Building>()
fun setDayLight(dayLight: Double) =
apply { this.dayLight = dayLight }
29/35 30/35
fun addLightSource(lightSource: LightSource) = And a final function to actually build the screen
apply { lightSources.add(lightSource) }
fun createScreen() = Screen(dayLight,
fun addNPC(npc: NPC) = lightSources,
apply { NPCs.add(npc)} NPCs,
buildings)
fun addBuilding(building: Building) =
apply { buildings.add(building) }
31/35 32/35
Example of usage Example of usage (2)
Since each function returned the builder, they can be concatenated
like this:
Using scope functions, we can even shorten this code:
fun mainScreen(): Screen {
val builder = ScreenBuilder() fun mainScreen(): Screen = with(ScreenBuilder()) {
setDayLight(12.0)
builder.setDayLight(12.0) addLightSource(LightSource(Point(10, 20), 2.4))
.addLightSource(LightSource(Point(10, 20), 2.4)) addBuilding(Building("Saloon"))
.addBuilding(Building("Saloon")) addBuilding(Building("Drugstore"))
.addBuilding(Building("Drugstore")) addNPC(Vampire("Dracula", Point(100, 100)))
.addNPC(Vampire("Dracula", Point(100, 100))) createScreen()
}
return builder.createScreen()
}
33/35 34/35
Further Information
35/35