Kotlin Programming 2020 Fall - Lecture 10
===
###### tags: `Kotlin`
This lecture intends to cover the materials in Chapter 16.
## Interface
+ Classes share common properties and functions: they all implement an interface.
+ Interface can specify a set of classes.
+ Interface has no constructor.
+ The classes implementing the same interface may still have much different nature.
+ We cannot generate a pure instance of any interface.
+ You may define a default implementation for a property or a function.
```kotlin=
enum class DamageStatus {
DESTROYED, SEVERE, MEDIUM, SLIGHT, UNDAMAGED
}
interface Destructible {
val damage: DamageStatus
val destroyed: Boolean
get() = damage == DamageStatus.DESTROYED
fun restore(cost: Int)
fun absorb(hit: Int)
}
interface Reporter {
fun report()
}
class Destroyer(val name: String, val maxHitPoint: Int, val power: Int) : Destructible, Reporter {
var hitPoint = maxHitPoint
override val damage: DamageStatus
get() = when (3 * hitPoint / maxHitPoint) {
3 -> DamageStatus.UNDAMAGED
2 -> DamageStatus.SLIGHT
1 -> DamageStatus.MEDIUM
else -> {
if (hitPoint == 0) DamageStatus.DESTROYED else DamageStatus.SEVERE
}
}
// Pay 100 to recover 1 hit point
override fun restore(cost: Int) {
// there is no way to restore a destroyed ship
if (destroyed) return
hitPoint = minOf(maxHitPoint, hitPoint + cost / 100)
}
// Every 500 hit reduce 1 hit point
override fun absorb(hit: Int) {
val damageHP = minOf(hitPoint, hit / 500)
hitPoint -= damageHP
}
// the "hit" generate by this destroyer
val attackPoint: Int
get() = when (damage) {
DamageStatus.UNDAMAGED -> 1000 * power
DamageStatus.SLIGHT -> 900 * power
DamageStatus.MEDIUM -> 700 * power
DamageStatus.SEVERE -> 100 * power
DamageStatus.DESTROYED -> 0
}
fun attack(obj: Destructible) {
obj.absorb(attackPoint)
}
override fun report() {
println("=======================================")
println("Destroyer $name:")
println("HP/MAX: $hitPoint/$maxHitPoint $damage")
println("Power/Attack Point: $power/$attackPoint")
}
}
class Bunker(val name: String) : Destructible, Reporter {
var new = true
override val damage: DamageStatus
get() = if (!new) DamageStatus.DESTROYED else DamageStatus.UNDAMAGED
// Any hit > 0 will destroy the bunker
override fun absorb(hit: Int) {
if (hit > 0) new = false
}
// Any cost > 0 will restore the bunker to undamaged
override fun restore(cost: Int) {
if (cost > 0) new = true
}
override fun report() {
println("=======================================")
println("$name is $damage.")
}
}
fun main() {
val hibiki = Destroyer("Hibiki", 20, 9)
val ikazuchi = Destroyer("Ikazuchi", 15, 8)
hibiki.report()
ikazuchi.report()
println("=======================================")
println("Ikazuchi attacks Hibiki")
ikazuchi.attack(hibiki)
hibiki.report()
println("=======================================")
println("Hibiki attacks Ikazuchi")
hibiki.attack(ikazuchi)
ikazuchi.report()
println("=======================================")
println("Restore Hibiki with 1000")
hibiki.restore(1000)
hibiki.report()
println("=======================================")
println("Hibiki attacks Ikazuchi again")
hibiki.attack(ikazuchi)
ikazuchi.report()
println("=======================================")
println("Restore Ikazuchi with 1000000000")
ikazuchi.restore(1_000_000_000)
ikazuchi.report()
val bunkerA = Bunker("Bunker A")
val bunkerB = Bunker("Bunker B")
println("=======================================")
println("Hibiki attacks Bunker A")
hibiki.attack(bunkerA)
bunkerA.report()
println("=======================================")
println("Ikazuchi attacks Bunker B")
ikazuchi.attack(bunkerB)
bunkerB.report()
println("=======================================")
println("restore Bunker A with 1")
bunkerA.restore(1)
bunkerA.report()
println("=======================================")
println("The following are destroyed.")
listOf(bunkerA, bunkerB, hibiki, ikazuchi)
.filter { (it as Destructible).destroyed }
.forEach { (it as Reporter).report() }
}
```
## Abstract class
+ An abstract class may have abstract properties and abstract functions that are not implemented in the definition of the abstract class.
+ Abstract properties / functions may not have implementation in the abstract classes, so they must be implemented in the subclasses.
+ We may use abstract class to define the common properties / functions of its subclass.
+ This may reduce repeatition of codes in subclasses.
+ But we must implement all abstract properties / functions in every non-abstract subclass.
+ Like interface, abstract classes may not create instances, since there may have some properties / functions not implemented yet.
+ Despite every abstract class has construcotrs, but these constructors are called by its subclasses and used for initialization of common parts among subclasses.
+ Why don't we just define the properties / functions in all subclasses instead of using abstract properties / functions?
+ We will need specific codes to deal with a specific subclass. And the codes may repeat a lot.
+ We want more flexibility.
+ Why not interface? Abstract classes may have back fields for properties. Note that a property of an interface may not use `field` -- Kotlin does not allow an interface to have back fields.
```kotlin=
enum class DamageStatus {
DESTROYED, SEVERE, MEDIUM, SLIGHT, UNDAMAGED
}
interface Destructible {
val damage: DamageStatus
val destroyed: Boolean
get() = damage == DamageStatus.DESTROYED
fun restore(cost: Int)
fun absorb(hit: Int)
}
interface Reporter {
fun report()
}
abstract class Ship(val name: String, val maxHitPoint: Int) : Destructible, Reporter {
var hitPoint = maxHitPoint
abstract val type: String
override val damage: DamageStatus
get() = when (3 * hitPoint / maxHitPoint) {
3 -> DamageStatus.UNDAMAGED
2 -> DamageStatus.SLIGHT
1 -> DamageStatus.MEDIUM
else -> {
if (hitPoint == 0) DamageStatus.DESTROYED else DamageStatus.SEVERE
}
}
// Pay 100 to recover 1 hit point
override fun restore(cost: Int) {
if (destroyed) {
println("There is no way to repair a destroyed ship.")
return
}
hitPoint = minOf(maxHitPoint, hitPoint + cost / 100)
println("$name is being repaired, and it's hit point becomes $hitPoint")
}
// Every 500 hit reduce 1 hit point
override fun absorb(hit: Int) {
val damageHP = minOf(hitPoint, hit / 500)
hitPoint -= damageHP
println("$name absorbs attacks and loses $damageHP hit points.")
}
override fun report() {
println("=======================================")
println("$type $name:")
println("HP/MAX: $hitPoint/$maxHitPoint $damage")
}
abstract fun attack(obj: Destructible)
abstract fun fix(obj: Destructible)
}
class Destroyer(name: String, maxHitPoint: Int, val power: Int) : Ship(name, maxHitPoint) {
override val type = "Destroyer"
val attackPoint: Int
get() = when (damage) {
DamageStatus.UNDAMAGED -> 1000 * power
DamageStatus.SLIGHT -> 900 * power
DamageStatus.MEDIUM -> 700 * power
DamageStatus.SEVERE -> 100 * power
DamageStatus.DESTROYED -> 0
}
override fun attack(obj: Destructible) {
if (destroyed) {
return println("$type $name cannot attack any other.")
}
println("$type $name attacks!")
obj.absorb(attackPoint)
}
override fun fix(obj: Destructible) {
println("A destroyer does not fix anything.")
}
override fun report() {
super.report()
println("Power/Attack Point: $power/$attackPoint")
}
}
class RepairShip(name: String, maxHitPoint: Int, val power: Int) : Ship(name, maxHitPoint) {
override val type = "Repair Ship"
override val damage: DamageStatus
get() = super.damage
val attackPoint: Int
get() = if (damage == DamageStatus.UNDAMAGED) 500 * power else 0
val fixPoint: Int
get() = when (damage) {
DamageStatus.UNDAMAGED -> 200 * power
DamageStatus.SLIGHT -> 100 * power
DamageStatus.MEDIUM -> 50 * power
DamageStatus.SEVERE -> 20 * power
DamageStatus.DESTROYED -> 0
}
override fun attack(obj: Destructible) {
if (destroyed) {
return println("$type $name cannot attack any other.")
}
println("$type $name attacks!")
obj.absorb(attackPoint)
}
override fun fix(obj: Destructible) {
if (destroyed) {
return println("$type $name cannot fix any other.")
}
println("$type $name start to fixes some destructible.")
obj.restore(fixPoint)
}
override fun report() {
super.report()
println("Power/Attack Point/Fix Point: $power/$attackPoint/$fixPoint")
}
}
class Bunker(val name: String) : Destructible, Reporter {
private var new = true
override val damage: DamageStatus
get() = if (!new) DamageStatus.DESTROYED else DamageStatus.UNDAMAGED
// Any hit > 0 will destroy the bunker
override fun absorb(hit: Int) {
if (hit > 0) new = false
}
// Any cost > 0 will repair the bunker to undamaged
override fun restore(cost: Int) {
if (cost > 0) new = true
}
override fun report() {
println("=======================================")
println("$name is $damage.")
}
}
fun main() {
val hibiki = Destroyer("Hibiki", 20, 9)
val ikazuchi = Destroyer("Ikazuchi", 15, 8)
val akashi = RepairShip("Akashi", 100, 3)
val ships = listOf(hibiki, ikazuchi, akashi)
ships.forEach { it.report() }
println("=======================================")
ikazuchi.attack(hibiki)
hibiki.report()
println("=======================================")
akashi.fix(hibiki)
hibiki.report()
println("=======================================")
hibiki.attack(akashi)
akashi.report()
val bunker = Bunker("Bunker")
println("=======================================")
akashi.attack(bunker)
bunker.report()
println("=======================================")
hibiki.attack(ikazuchi)
ikazuchi.report()
println("=======================================")
println("Report the damage status of the ships:")
ships.forEach {
println("${it.type} ${it.name}: ${it.damage}")
}
}
```