Inheritance

Scala Type Hierarchy

Hierarchy

  • All objects share Any as their base types
  • Classes extending AnyVal will be stored on the stack
  • Classes extending AnyRef will be stored on the heap

Overview

  • Let’s do some world building

  • If we’re making a game, we’ll want various objects that will interact with each other

  • we’ll setup a simple game where

    • Each player has a set health and strength
    • Players can pick up and throw balls
    • If a player gets hit with a ball, they lose health
    • Players can collect health potions to regain health
  • Note: We might not build this full game, but we will build some of the game mechanics.

Objects Review

  • We’ll need different objects for this game
    • Player
    • Ball
    • HealthPotion

LQ2-1-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
object Player {
var location: PhysicsVector = new PhysicsVector(2.0, -2.0, 2.0)
var dimensions: PhysicsVector = new PhysicsVector(1.0, 1.0, 2.0)
var velocity: PhysicsVector = new PhysicsVector(0.0, -1.0, 0.0)
var orientation: PhysicsVector = new PhysicsVector(0.5, -1.5, 0.0)

val maxHealth: Int = 20
val strength: Int = 25

var health: Int = 17

def useBall(ball: Ball): Unit = {
ball.use(this)
}

def use HealthPotion(potion: HealthPotion): Unit = {
potion.use(this)
}
}

lq2-1-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object ball {
var location: PhysicsVector = new PhysicsVector(1.0, 5.0, 2.0)
var dimensions: PhysicsVector = new PhysicsVector(1.0, 1.0, 1.0)
val velocity: PhysicsVector = new PhysicsVector(1.0, 1.0, 10.0)
val mass: Double = 5.0

def use(player: Player): Unit = {
this.velocity = new PhysicsVector(
player.orientation.x * player.strength,
player.orientation.y * player.strength,
player.strength
)
}
}

lq2-1-3

1
2
3
4
5
6
7
8
9
10
object HealthPotion {
var location: PhysicsVector = new PhysicsVector(5.0, 7.0, 0.0)
var dimensions: PhysicsVector = new PhysicsVector(1.0, 1.0, 1.0)
var velocity: PhysicsVector = new PhysicsVector(0.0, 0.0, 0.0)
val volume: Int = 3

def use(player: Player): Unit = {
player.health = (player.health + this.volume).min(player.maxHealth)
}
}
  • But this is restrictive
  • Game can only have one Ball, one HealthPotion and on Player
  • Can play, but mot very fun

Classes Review

lq2-1-4

  • This is why we use classes
  • Classes let use create multiple objects of type Ball, HealthPotion, and Player.

lq2-1-5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Player(var location: PhysicsVector,
var dimensions: PhysicsVector,
var velocity: PhysicsVector,
var orientation: PhysicsVector,
val maxHealth: Int,
val strength: Int) {

var health: Int = maxHealth

def useBall(ball: Ball): Unit = {
ball.use(this)
}

def useHealthPotion(potion: HealthPotion): Unit = {
potion.use(this)
}
}

lq2-1-6

1
2
3
4
5
6
7
8
9
10
11
12
13
class Ball(var location: PhysicsVector,
var dimensions: PhysicsVector,
var velocity: PhysicsVector,
val mass: Double) {

def use(player: Player): Unit = {
this.velocity = new PhysicsVector(
player.orientation.x * player.strength,
player.orientation.y * player.strength,
player.strength
)
}
}

lq2-1-7

1
2
3
4
5
6
7
8
9
class HealthPotion(var location: PhysicsVector,
var dimensions: PhysicsVector,
var velocity: PhysicsVector,
val volume: Int) {

def use(player: Player): Unit = {
player.health = (player.health + this.volume).min(player.maxHealth)
}
}

lq2-1-6

  • Use the class to create multiple objects with different states

lq2-1-8

1
2
3
4
5
6
7
var ball1: Ball = new Ball(
new PhysicsVector(1.0, 5.0, 2.0),
new PhysicsVector(1.0, 1.0, 1.0),
new PhysicsVector(1.0, 1.0, 10.0),
5.0
)
// ball1 stores 54224

lq2-1-9

1
2
3
4
5
6
7
var ball2: Ball = new Ball(
new PhysicsVector(6.0, -3.0, 2.0),
new PhysicsVector(1.0, 1.0, 1.0),
new PhysicsVector(0.0, 4.5, 4.5),
10.0
)
// ball2 stores 21374

Inheritance

  • Use inheritance to create classes with different behavior
  • Observe: Ball and HealthPotion have a lot in common

lq2-1-6
lq2-1-7

  • Can add much more common functionality (that doesn’t fit on a slide)
    • Compute mass of a potion based on volume
    • Compute momentum of both types based on mass * velocity
    • Method defining behavior when either hits the ground(bounce or shatter)

lq2-1-10

  • Factor out common state and behavior into a new calss

  • Ball and HealthPotion calsses inherent the state and behavior of InanimateObject

  • Ball and HealthPotion add their specific state and behavior

  • New class defines what every inheriting class must define

  • Any behavior that is to be defined by inheriting classes is declared abstract

    • We call this an abstract class
    • Cannot create objects of abstract
  • Inheriting classes will define all abstract behavior

    • We call these concrete classes
      1
      2
      3
      4
      5
      6
      abstract class InanimateObject(var location: PhysicsVector, var dimension: PhysicsVector, var velocity: PhysicsVector){

      def objectMass(): Double

      def use(player: Player): Unit
      }
  • Use the extends keyword to inherent another class

    • Extend the definition of InanimateObject
    • We call IanimateObject the superclass of Ball
      1
      2
      3
      4
      5
      6
      abstract class InanimateObject(var location: PhysicsVector, var dimension: PhysicsVector, var velocity: PhysicsVector){

      def objectMass(): Double

      def use(player: Player): Unit
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Ball(location: PhysicsVector,
      dimensions: PhysicsVector,
      velocity: PhysicsVector,
      mass: Double)
      extends InanimateObject(location, dimensions, velocity){

      override def objectMass(): Double = {
      this.mass
      }

      override def use(player: Player): Unit = {
      this.velocity.x = player.orientation.x * player.strength
      this.velocity.y = player.orientation.y * player.strength
      this.velocity.z = player.strength
      }

      }
  • Ball has it’s own constructor

  • Ball must call InanimateObject’s constructor

  • var / val declared in concrete class to make these public

  • Implement all abstract behavior

  • Use the override keyword when overwritting behavior from the superclass

  • Override all abstract methods with behavior for this class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    abstract class InanimateObject(var location: PhysicsVector, var dimension: PhysicsVector, var velocity: PhysicsVector){

    def objectMass(): Double

    def use(player: Player): Unit
    }
    ```
    ```scala
    class HealthPotion(location: PhysicsVector,
    dimensions: PhysicsVector,
    velocity: PhysicsVector,
    val volume: Int)
    extend InanimateObject(location, dimensions, velocity) {

    override def objectMass(): Double = {
    val massPerVolume: Double = 7.0
    volume * massPerVolume
    }

    override def use(player: Player): Unit = {
    player.health = (player.health + this.volume).min(player.macHealth)
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Ball(location: PhysicsVector,
    dimensions: PhysicsVector,
    velocity: PhysicsVector,
    mass: Double)
    extend InanimateObject(location, dimensions, velocity){

    override def objectMass(): Double = {
    this.mass
    }

    override def use(player: Player): Unit = {
    this.velocity.x = player.orientation.x * player.strength
    this.velocity.y = player.orientation.y * player.strength
    this.velocity.z = player.strength
    }
    }

  • Define different behavior for each base class

  • Define similar types with some difference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class InanimateObject(var location: PhysicsVector, var dimensions: PhysicsVector, var velocity: PhysicsVector) {

def objectMass(): Double

def use(player: Player): Unit

def magnitudeOfMomentum(): Double = {
val magnitudeOfVelocity = Math.sqrt(
Math.pow(this.velocity.x, 2.0) +
Math.pow(this.velocity.y, 2.0) +
Math.pow(this,velocity.z, 2.0)
)
magnitudeOfVelocity * this.objectMass()
}
}
  • OK, BUT Y THO?

  • Add behavior to InanimateObject

  • Behavior is added to ALL inheriting classes

  • We may want many, many more subtypes of InanimateObjects in our game

  • Any common functionality added to InanimateObject

    • Easy to add functionality to ALL subtypes will very little effort
  • But wait!

    • There’s more
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class InanimateObject(var location: PhysicsVector, var dimensions: PhysicsVector, var inputVelocity: PhysicsVector) extends DynamicObject(location, dimensions){

this.velocity = inputVelocity

def objectMass(): Double

def use(player: Player): Unit

def magnitudeOfMomentum(): Double = {
val magnitudeOfVelocity = Math.sqrt(
Math.pow(this.velocity.x, 2.0) +
Math.pow(this.velocity.y, 2.0) +
Math.pow(this,velocity.z, 2.0)
)
magnitudeOfVelocity * this.objectMass()
}
}
  • If we want Ball, HealthPotion, and all other InanimateObjects to work with our physics engine

    • Extend DynamicObject!
  • Note that the velocity is inherited

  • The velocity parameter in the constructor must have a different name that the inherited variable

    • Allows us to assign its value to the state variable
    • They would both be referred to with this causing a name conflict
  • No name conflict with multiple location / dimension since they are only in the header

lq2-1-11

Scala Type Hierarchy

lq2-1-12

  • All objects share Any as their base types
  • Classes extending AnyVal will be stored on the stack
  • Classes extending AnyRef will be stored on the heap

lq2-1-13

  • Classes you define extend AnyRef by default
  • HealthPotion has 6 different types
    1
    2
    3
    4
    5
    6
    val potion1: HealthPotion = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6) 
    val potion2: InanimateObject = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6)
    val potion3: DynamicObject = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6)
    val potion4: GameObject = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6)
    val potion5: AnyRef = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6)
    val potion6: Any = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6)

Lecture Question

Question: in a package named “oop.electronics”, implement the following. This functionality is similar to the last lecture question

  • class Battery with
    • A constructor that takes a variable named “charge” of type Int
  • abstract class Electronic with
    • A constructor that takes no parameters
    • A state variable named “battery” of type Battery
    • A method named “use” that takes no parameters and returns Unit (This can be abstract)
    • A method named “replaceBattery” that takes a Battery as a parameter and returns a Battery
      • This method swaps the input Battery with the Battery currently stored in this Electronic’s state variable
      • The returned Battery is the one that was in the state variable when the method is called
  • class Flashlight that extends Electronic
    • A constructor that takes no parameters
      • When a new Flashlight is created, assign the inherited state variable named “battery” to a new Battery with 5 charge (ie. Batteries included)
    • Override the “use” method to reduce the charge of the battery in the state variable by 1 if its charge is 1 or greater
  • class BoomBox that extends Electronic
    • A constructor that takes a variable of type Battery and assigns it to the inherited state variable named “battery”
      • Your BoomBox constructor parameter should have a different name than the state variable
    • Override the “use” method to reduce the charge of the battery in the state variable by 3 if its charge is 3 or greater

Hint
The code is in a different package so it doesn’t interfere with your code from the previous question. Be sure you check that this import works from a different package in your project

1
import oop.electronics.{Battery, BoomBox, Flashlight, Electronic}

Your Flashlight and BoomBox classes must inherit Electronic. This will be checked by storing them in variables of type Electronic

1
2
val flashlight1: Electronic = new Flashlight()
val boomBox1: Electronic = new BoomBox(new Battery(10))