OOP Advanced Lesson
Return to main OOP lesson page
Lesson 1 — Advanced Inheritance: Building Complex Class Hierarchies
Big picture
- Advanced Inheritance involves creating multiple levels of class hierarchies and understanding when to use inheritance vs. composition.
- In this enhanced project, we have base classes (
GameObject
,Brick
) and specialized subclasses (StrongBrick
,MovingBrick
) that demonstrate different inheritance patterns. - Polymorphism allows different brick types to behave differently while sharing the same interface.
Enhanced base classes
GameObject - The foundation
class GameObject {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(ctx) {
// Base draw method - to be overridden
}
update() {
// Base update method - to be overridden
}
// NEW: Enhanced collision detection
getBounds() {
return {
left: this.x,
right: this.x + (this.width || this.radius * 2),
top: this.y,
bottom: this.y + (this.height || this.radius * 2)
};
}
}
This enhanced base class now includes collision bounds calculation, making it more useful for subclasses.
Brick - The specialized base class
class Brick extends GameObject {
constructor(x, y, width = 75, height = 20) {
super(x, y);
this.width = width;
this.height = height;
this.status = 1; // 1 = active, 0 = destroyed
this.hasPowerUp = Math.random() < 0.3;
this.color = "#0095DD";
}
hit() {
this.status = 0;
return true; // Brick is destroyed
}
getPoints() {
return 1; // Base points for hitting this brick
}
}
Brick
inherits from GameObject
but adds brick-specific behavior. This creates a two-level inheritance hierarchy.
Advanced subclasses
StrongBrick - Multiple inheritance levels
class StrongBrick extends Brick {
constructor(x, y, width = 75, height = 20) {
super(x, y, width, height);
this.maxHits = 2;
this.hits = this.maxHits;
this.color = "#ff6b35";
}
hit() {
this.hits--;
if (this.hits <= 0) {
this.status = 0;
return true; // Brick is destroyed
}
return false; // Brick still alive
}
getPoints() {
return 2; // Override parent method
}
}
StrongBrick
extends Brick
, which extends GameObject
- this is a three-level inheritance hierarchy. It overrides both hit()
and getPoints()
methods.
Try it: Create an UltraStrongBrick
that extends StrongBrick
and requires 3 hits. How would you modify the constructor and hit()
method?
Lesson 2 — Polymorphism and Method Overriding
What is polymorphism?
Polymorphism means “many forms” - it allows objects of different classes to be treated the same way while behaving differently. In our game, all brick types can be treated as “bricks” but each responds differently to being hit.
Method overriding in action
Drawing differences - Each brick type looks different
// Basic Brick - simple rectangle
draw(ctx) {
if (this.status === 1) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
// StrongBrick - shows remaining hits
draw(ctx) {
if (this.status === 1) {
// Dynamic color based on remaining hits
const alpha = this.hits / this.maxHits;
const r = 255;
const g = Math.floor(107 * alpha);
const b = Math.floor(53 * alpha);
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.fillRect(this.x, this.y, this.width, this.height);
// Draw hit counter
ctx.fillStyle = "white";
ctx.font = "bold 12px Arial";
ctx.textAlign = "center";
ctx.fillText(this.hits.toString(), this.x + this.width/2, this.y + this.height/2 + 4);
}
}
Both methods are called draw()
, but they produce different visual results. This is method overriding.
Movement behavior - MovingBrick adds update logic
class MovingBrick extends Brick {
constructor(x, y, width = 75, height = 20) {
super(x, y, width, height);
this.speed = 1;
this.direction = Math.random() > 0.5 ? 1 : -1;
this.originalX = x;
this.moveRange = 50;
}
update(canvasWidth) {
if (this.status === 1) {
this.x += this.speed * this.direction;
// Reverse direction at boundaries
if (Math.abs(this.x - this.originalX) > this.moveRange ||
this.x <= 0 || this.x >= canvasWidth - this.width) {
this.direction *= -1;
}
}
}
}
MovingBrick
overrides the base update()
method to add movement behavior that other bricks don’t have.
Polymorphism in the Game class
Factory method - Creating different types
createRandomBrick(x, y) {
const random = Math.random();
if (random < 0.6) {
return new Brick(x, y);
} else if (random < 0.85) {
return new StrongBrick(x, y);
} else {
return new MovingBrick(x, y);
}
}
This method can return different brick types, but they’re all stored in the same bricks
array and treated uniformly.
Uniform handling - Same interface, different behavior
// In the game loop - all bricks are treated the same way
for (let brick of this.bricks) {
if (brick.isActive() && ball.collidesWith(brick)) {
const destroyed = brick.hit(); // Calls appropriate hit() method
if (destroyed) {
this.score += brick.getPoints(); // Calls appropriate getPoints() method
}
}
}
The game doesn’t need to know what type of brick it’s dealing with - each brick responds to hit()
and getPoints()
appropriately.
Try it: Add a getDescription()
method to each brick class that returns a different string (“Basic”, “Strong”, “Moving”). Then log these descriptions during collision detection.
Lesson 3 — Composition and Array Management with Multiple Object Types
Advanced composition patterns
The Game
class demonstrates composition by managing collections of different object types. This is more complex than simple inheritance because it involves coordinating multiple arrays of different classes.
Multiple object collections
class Game {
constructor(canvasId) {
// ... initialization ...
// Multiple arrays for different object types
this.balls = [new Ball(this.width / 2, this.height - 30)]; // Array of balls
this.paddle = new Paddle(/* ... */); // Single paddle
this.bricks = []; // Mixed brick types
this.powerUps = []; // Array of power-ups
// Complex state management
this.activePowerUps = new Set(); // Set for active power-up types
this.powerUpTimers = {}; // Object for timing
}
}
The game manages four different collections with different data structures (arrays, single objects, Sets, and plain objects).
Array management patterns
Filtering active objects
updatePowerUps() {
for (let powerUp of this.powerUps) {
powerUp.update(this.height);
if (powerUp.collidesWithPaddle(this.paddle)) {
powerUp.collect();
this.applyPowerUp(powerUp.type);
}
}
// Remove inactive power-ups
this.powerUps = this.powerUps.filter(p => p.active);
}
This pattern updates all objects, then filters out inactive ones. It’s common when objects can become inactive during the game.
Mixed-type array handling
update() {
// Update moving bricks only (using instanceof)
for (let brick of this.bricks) {
if (brick instanceof MovingBrick) {
brick.update(this.width);
}
}
}
Since the bricks
array contains different types, we use instanceof
to identify which objects need special handling.
Dynamic object creation and removal
applyPowerUp(type) {
if (type === "multiball") {
// Add 2 extra balls dynamically
for (let i = 0; i < 2; i++) {
const newBall = new Ball(this.balls[0].x, this.balls[0].y);
const angle = (Math.PI / 4) * (i + 1);
const speed = Math.hypot(this.balls[0].dx, this.balls[0].dy);
newBall.dx = speed * Math.cos(angle) * (Math.random() > 0.5 ? 1 : -1);
newBall.dy = -speed * Math.sin(angle);
this.balls.push(newBall); // Add to existing array
}
}
}
checkBallCollision() {
let activeBallCount = 0;
for (let ball of this.balls) {
if (!ball.active) continue;
activeBallCount++;
// ... collision logic ...
}
// Clean up inactive balls by creating new array
if (activeBallCount === 0) {
this.balls = [new Ball(this.width / 2, this.height - 30)];
}
}
Objects can be added dynamically (push()
) or replaced entirely by creating a new array.
Try it: Create a method getBrickTypeCount()
that returns an object with counts of each brick type currently active. Use instanceof
to identify types.
Lesson 4 — Advanced Object Communication and State Management
Power-up system architecture
The power-up system demonstrates complex object communication where multiple objects need to coordinate their behavior.
Power-up creation chain
// 1. Brick determines if it has a power-up (in constructor)
this.hasPowerUp = Math.random() < 0.3;
// 2. When brick is destroyed, Game creates power-up
if (destroyed && brick.hasPowerUp) {
const powerUpType = this.getRandomPowerUpType();
this.powerUps.push(new PowerUp(brick.x + brick.width / 2, brick.y, powerUpType));
}
// 3. Power-up falls and checks collision with paddle
collidesWithPaddle(paddle) {
return (
this.active &&
this.y + this.size / 2 >= paddle.canvasHeight - paddle.height &&
this.x > paddle.x &&
this.x < paddle.x + paddle.width
);
}
// 4. Game applies power-up effects
this.applyPowerUp(powerUp.type);
This shows how four different classes communicate: Brick
→ Game
→ PowerUp
→ Game
→ Paddle
.
Complex state management
Timer-based effects
class Game {
constructor() {
// Complex state tracking
this.activePowerUps = new Set(); // Which power-ups are active
this.powerUpTimers = {}; // When each was activated
this.powerUpDurations = { // How long each lasts
wide: 5000,
speed: 3000,
multiball: 1000
};
}
updatePowerUps() {
// Check timers for all active power-ups
for (let type of this.activePowerUps) {
const elapsed = Date.now() - this.powerUpTimers[type];
if (elapsed > this.powerUpDurations[type]) {
this.activePowerUps.delete(type);
delete this.powerUpTimers[type];
this.removePowerUp(type); // Clean up effects
}
}
}
}
This uses three different data structures to track power-up state: a Set for active types, an Object for timers, and another Object for durations.
State restoration
removePowerUp(type) {
if (type === "wide") {
this.paddle.resetPowerUp(); // Tell paddle to reset
} else if (type === "speed") {
for (let ball of this.balls) {
ball.speedUp(1.43); // Restore normal speed
}
}
// multiball doesn't need removal - balls naturally disappear
}
The game needs to undo power-up effects by communicating with the affected objects.
Factory pattern for object creation
Power-up type selection
getRandomPowerUpType() {
const types = ["wide", "speed", "multiball"];
const probabilities = [0.4, 0.3, 0.3];
const random = Math.random();
let cumulative = 0;
for (let i = 0; i < types.length; i++) {
cumulative += probabilities[i];
if (random < cumulative) {
return types[i];
}
}
return types[0];
}
This factory method encapsulates the logic for creating different power-up types with weighted probabilities.
Brick creation factory
createRandomBrick(x, y) {
const random = Math.random();
if (random < 0.6) {
return new Brick(x, y);
} else if (random < 0.85) {
return new StrongBrick(x, y);
} else {
return new MovingBrick(x, y);
}
}
This factory abstracts the complexity of choosing which brick type to create.
Try it: Create a PowerUpFactory
class that handles power-up creation logic. Move the getRandomPowerUpType()
method into this factory class.
Wrap-up: Advanced OOP Concepts in Action
- Multi-level Inheritance:
GameObject
→Brick
→StrongBrick
creates complex hierarchies - Polymorphism: Different brick types respond differently to the same method calls
- Advanced Composition: Game manages multiple arrays of different object types
- Object Communication: Complex chains of interaction between different classes
- State Management: Sophisticated tracking of temporary effects and timers
- Factory Patterns: Centralized creation logic for complex object types
This advanced version demonstrates how OOP concepts scale to handle complex game mechanics while keeping code organized and maintainable.
ACTIVITY: Advanced Inheritance Diagram
Draw your understanding of the advanced class hierarchy and object relationships.
Drawing Guide:
- Draw inheritance arrows:
GameObject
→Brick
→StrongBrick
- Show composition:
Game
contains arrays of different objects - Show communication: arrows between classes that interact
- Use different colors for different concept types
Press r
for red, b
for blue, g
for green, y
for yellow, p
for purple, c
to clear.
✅ Advanced Checkpoint Quizzes
getPoints()
method?update()
method?Advanced Challenges
Now that you understand the advanced concepts, try these challenges:
- Create an ExplosiveBrick that destroys adjacent bricks when hit
- Add a BrickFactory class that manages all brick creation logic
- Implement a PowerUpManager to handle complex power-up interactions
- Design a particle system for visual effects using inheritance
- Create a save/load system that preserves the game state
These challenges will test your understanding of advanced OOP concepts and push you to think about scalable, maintainable code architecture.