Programming technique · B3.2.2 · Higher level only
Polymorphism
Inheritance gives related classes a shared role. Polymorphism becomes useful when the caller can use that shared role while each actual object supplies the behaviour that is right for its own type.
Why not ask the caller what type every object is?
Imagine a loop processing several related object types. A brittle design makes the loop inspect every object and decide what to do. A polymorphic design gives the related classes a common operation and lets each object provide the appropriate implementation.
Type-checking caller
The caller needs knowledge of Dog, Human, Bird and every future subtype. Adding a subtype means revisiting the dispatch logic and creates another place where a case can be forgotten.
Polymorphic caller
The caller asks every Animal for activity(). Each subtype owns its specialised implementation. New subtypes can join the same caller without rewriting the loop.
Dynamic polymorphism: override the inherited method
In Java, a child class can override an inherited instance method. @Override asks the compiler to verify that a matching parent method really exists. The important runtime idea is that an Animal reference may point to a Dog, Human or Animal object.
In Python, normal method lookup is dynamic. A subclass can define a method with the same name as the parent implementation. When the method is called, Python looks at the actual object and finds the appropriate implementation; there is no Java-style @Override annotation.
One caller, mixed objects
Every element in the array is used through the shared Animal role. The call expression stays exactly the same: member.activity(). Runtime dispatch selects the implementation from the actual object.
The list can contain related objects with the same expected operation. The loop does not need a declared parent reference type, but the call expression still stays exactly the same: member.activity().
| Expression | Declared/reference type | Actual object type | Implementation selected |
|---|---|---|---|
group[0].activity() | Animal | Dog | Dog.activity() |
group[1].activity() | Animal | Human | Human.activity() |
group[2].activity() | Animal | Animal | Animal.activity() |
| Expression | Actual object type | Implementation selected |
|---|---|---|
group[0].activity() | Dog | Dog.activity() |
group[1].activity() | Human | Human.activity() |
group[2].activity() | Animal | Animal.activity() |
Overriding is not overloading
The guide also requires static polymorphic behaviour. A common Java example is method overloading: the class has the same method name with different parameter lists, and the matching signature is selected from the call and available types. This is a different mechanism from runtime overriding.
| Mechanism | What changes? | When is the choice made? | Core example |
|---|---|---|---|
| Method overriding | Subclass supplies its own inherited implementation | Runtime, from the actual object | Animal member = new Dog(...) then member.activity() |
| Java method overloading | Same class exposes the same method name with different parameter lists | Before runtime dispatch, from the applicable signature | label("Ari") versus label("Ari", 3) |
Why polymorphism improves flexibility
Caller stays stable
A new subtype can provide the common operation without forcing every caller to add another type test.
Behaviour stays with the class
The code that makes a Dog behave like a Dog belongs in Dog rather than in a distant selection structure.
Common code is reusable
Collections, loops and methods can work with the shared parent role instead of duplicating logic for each subtype.
Still requires a sound hierarchy
Polymorphism does not rescue a poor inheritance design. The subtype must still make sense wherever the parent role is expected.
Trace the dispatch
Predict first. Reveal only after you can justify which method implementation is selected.
-
Java: Animal member = new Dog("Mochi"); Which activity() implementation runs, and why?
Reveal model answer
Dog.activity() runs. The variable is declared as Animal, but the object referenced at runtime is a Dog, so dynamic dispatch selects the Dog override.
-
If Human does not override activity(), what happens when a Human object receives activity()?
Reveal model answer
The inherited Animal.activity() implementation runs. Overriding is optional unless a later design, such as an abstract class, requires an implementation.
-
A Bird subclass is added to the group. What should change in a well-designed polymorphic caller?
Reveal model answer
Add the Bird class, its activity() override and a Bird object. The loop that calls member.activity() should not need a Bird-specific type check.
-
What is the key difference between Java method overloading and method overriding?
Reveal model answer
Overloading selects between different parameter signatures before runtime dispatch; overriding selects a subclass implementation at runtime based on the actual object. Python does not provide Java-style signature-based overloading.
What you should be able to construct
For this standard, recognition is not enough. You should be able to write a small parent–child hierarchy, override a shared operation, place different concrete objects behind a shared caller, and prove that one unchanged method call produces subtype-specific behaviour. In Java, you should also be able to apply a small overloaded-method example and distinguish it from overriding.
Challenges Choose one
Choose a challenge that feels appropriate for you. Code heat is only a rough estimate, not a fixed level.
Complete the Runtime Dispatch
SelectedComplete Dog and Human so that each overrides Animal.activity(). Then process Dog, Human and plain Animal objects through one Animal[] collection and one unchanged activity() call. Predict the three output lines before running the program. Do not use instanceof or type-checking selection to choose the behaviour.
Add a New Form Without Changing the Loop
SelectedExtend the supplied hierarchy with Bird. Override activity(), add one Bird object to the existing Animal[] collection and prove that the existing loop works without modification. Add a short comment explaining how this demonstrates flexibility and reusability, and why adding a new subtype should not require another instanceof branch.
Static or Dynamic?
SelectedComplete a Java ScoreLabel class with two overloaded label methods: one taking an int and one taking an int plus a String suffix. Run both calls, then comment on when Java chooses the overload and how this differs from runtime method overriding. Keep the example small: two signatures are enough.
Selected challenge
This choice is shared with the portfolio setup page.
Plan your solution in handwritten pseudocode
Before opening your IDE or writing any program code, handwrite pseudocode for this challenge on paper.
Not marked complete. If you submit now, the GitHub README will record “No”.
Create your challenge folder
Run this command after planning. It creates the correct empty folder inside your portfolio.
Complete the challenge
Use your handwritten pseudocode as the starting plan, then write and test your solution in the folder created above.
Optional two-level scaffold
Try from your handwritten pseudocode first. Scaffold gives some structure; Scaffold + comments gives stronger guidance. Use only the level you need, and update your pseudocode first if the support changes your plan.
Submit for review
Run this when your program is complete. It creates the README, commits the folder and pushes it. The README records whether you marked the handwritten pseudocode as complete; the paper itself is handed to your teacher separately.