CSE116的学习笔记 Lec2-2:Polymorphism
Polymorphism
Scala Type Hierarchy-1
- All objects share Any as their base typoes
- Classes extending AnyVal will be stored on the stack
- *Unless they are a state variable of an object
- Classes extending AnyRef will be stored on the heap
Recall
Scala Type Hierarchy-2
- Classes you define extend AnyRef by default
- HealthPotion has 6 different types
1
2
3
4
5
6val 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)
Polymorphism
HealthPotion has 6 different types
Polymorphism
- Poly -> Many
- Morph -> Forms
- Polymorphism -> Many Forms
Can store values in variables of any of their types
Can only access state and behavior defined in variable type
Defined magnitudeOfMomentum in lnanimateObject
HealthPotion inherited magnitudeOfMomentum when it extended InanimateObject
DynamicObject has no such method
- Even when potio3 stores a reference to a HealthPotion object it cannot access magnitudeOfMomentum
1 | val potion1: HealthPotion = new HealthPotion(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 6) |
- Why use polymorphism if it restricts functionality?
- Simplify other classes
- Player has 2 methods
- One to use a ball
- One to use a potion
- Each item the Player can use will need another method in the Player class
- Tedious to expand game
1 | class Player(var location: PhysicsVector, var dimensions: PhysicsVector, var velocity: PhysicsVector, var orientation: PhysicsVector, val maxHealth: Int, val strength: Int) { |
- Write functionality using the common base type
- The use method is part of IanimateObject
- Can’t access any Ball or HealthPotion specific functionlity
- Any state / behavior needed by Player must be in the InanimateObject class
Old version:
1 | class Player(var location: PhysicVector, |
Change to this:
1 | abstract class InanimateObject(location: PhysicsVector, velocity: PhysicVector) { |
1 | class Player(var location: PhysicVector, |
- We can call useItem with any object that extends InanimateObject as an argument
- The useItem method will have different effrcts depending on the type of its parameter
- Different implementations of use will be called
- Adding new object types to our game does not require changing the Player class!
- Test Player once
- Without polymorphism we’d have to update and test the Player class for every new object type added to the game.
1 | abstract class InanimateObject(location: PhysicsVector, velocity: PhysicVector) { |
1 | class Player(var location: PhysicVector, |
1 | val ball: Ball = new Ball(new PhysicsVector(), new PhysicsVector(), new PhysicsVector(), 5) |
- We can also make our player be a DynamicObject
Original:
1 | class Player(var location: PhysicVector, |
Extends the DynamicObject:
1 | class Player(var location: PhysicVector, |
- With polymorphism, we can mix types in data structures
- Something we took for granted in Python / JavaScript
- PhysicsEngine.updateWorld does not care about the types in world.object
- As long as they all have DynamicObject as a superclass
1 | val player: Player = new Player(new PhysicsVector(0.0, 0.0, 0.0), new PhysicsVector(1.0, 1.0, 2.0), new PhysicsVector(0.0, 0.0, 0.0), new PhysicsVector(1.0, 0.0, 0.0), 10, 255) |
Override
- Functionality is inherited from Any and AnyRef
- println calls an inherited .toString method
- Converts object to a String with
@
- Converts object to a String with
- == calls the inherited .equals method
- returns true only if the two variables refer to the same object in memory
1 | val potion1: HealthPotion = new HealthPotion(new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), 4) |
- We can override this default functionality
- Override toString to return a different string
1 | class HealthPotion(location: PhysicsVector, dimensions: PhysicsVector, |
1 | class PhysicsVector(var x: Double, var y: Double, var z: Double) { |
- Override equals to change the definition of equality
- Takes Any as a parameter
- Use match and case to behave differently on different types
- The _ wildcard covers all types not explicitly mentioned (“_” 跟 else 一个性质, 或者说switch里的default)
- This method return true when compared to another potion with the same volume, false otherwise
1 | class HealthPotion(location: PhysicsVector, |
- With our overridden methods this code ggives very different output
1
2
3
4
5
6
7
8
9
10
11val potion1: HealthPotion = new HealthPotion(new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), 4)
val potion2: HealthPotion = new HealthPotion(new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), new PhysicsVector(0,0,0), 4)
val potion3 = potion1
println(potion1) // week4.oop_physics.with_oop.HealthPotion@17c68925
println(potion2) // week4.oop_physics.with_oop.HealthPotion@7e0ea639
println(potion3) // week4.oop_physics.with_oop.HealthPotion@17c68925
println(potion1 == potion2) // true (上文写的 override def equals)
println(potion1 == potion3) // true
Override in Jumper
To create a platform in the jumper game
- Extend JumperObject which extends StaticObject
- Platforms are now StaticObject and are compatible with your PhysicsEngine
- Override collideWithDynamicObject to define how an object reacts to a collision with a Platforn
- If the colliding face is the top, the object lands on the Platform
1 | class JumperObject(location: PhysicsVector, dimensions: PhysicsVector) extends StaticObject(location, dimensions) { |
1 | class Platform(location: PhysicsVector, dimensions: PhysicsVector) extend JumperObject(location, dimensions) { |
Similar method used to create Walls
Now all dynamic objects in our game react properly to wall and platform collisions as long as they extend DynamicObject
1 | class JumperObject(location: PhysicsVector, dimensions: PhysicsVector) extends StaticObject(location, dimensions) { |
1 | class Wall(location: PhysicsVector, dimensions: PhysicsVector) extends JumperObject(location, dimensions) { |
Lecture Quesion
Question: in a package named “oop.electronics”, implement the following to expand the Flashlight/BoomBox functionality from the previous lecture question. Full functionality from the previous lecture question is required
- classes Battery, Electronic, BoomBox, and Flashlight as defined in the previous lecture question
- An obeject named UseElectronics with
- A method named “useAll” that takes a List of Electronics as a parameter and returns Unit
- Calls the “use” method on all the Electronics in the input list
- [Notice that the specific method that is called depends on whether the Electronic is a BoomBox or a Flashlight]
- A method named “swapBatteries” that takes two Electronics as parameters and returns Unit
- Exchanges the batteries between the two Electronics
- A method named “useAll” that takes a List of Electronics as a parameter and returns Unit
Testing: In a package named “tests” create a Scala class named “TestElectronics” as a test suite that tests all the functionality listed above