Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
306: Composition Over Inheritance in Practice
I want to show you a snippet of code I inherited from a junior dev a few years ago. They were building a simple RPG combat system. On the surface, it looks logical: a Character base class, and then specific roles like Warrior and Mage inheriting from it. But look at what happened as the game grew.
public class GameCharacter {
public String name;
public void attack() {
System.out.println(name + " attacks!");
}
// Added later because Mages needed it
public void castSpell() {
throw new UnsupportedOperationException("This character cannot cast spells");
}
}
public class Warrior extends GameCharacter {
@Override
public void attack() {
System.out.println(name + " swings a massive sword!");
}
}
public class Mage extends GameCharacter {
@Override
public void castSpell() {
System.out.println(name + " casts Fireball!");
}
}
public class Paladin extends Warrior {
// Paladins are Warriors, but they also need to cast spells.
// But we can't inherit from Mage too!
@Override
public void castSpell() {
System.out.println(name + " casts Holy Light!");
}
}
The "Junk-Drawer" Base Class
Do you see the problem here? The developer hit a wall with Java's single inheritance. When the Paladin arrived—a character that is both a fighter and a spellcaster—they realized they couldn't inherit from both Warrior and Mage. To "fix" this, they started pushing methods like castSpell() up into the GameCharacter base class.
This is a classic trap. Now, every single character in the game—even a simple peasant or a dog—inherits a castSpell() method they can't actually use. We've created a "junk-drawer" base class. The moment you call castSpell() on a Warrior, the program crashes with an UnsupportedOperationException. We've violated the Liskov Substitution Principle; a Warrior is no longer a reliable substitute for a GameCharacter because it breaks the contract of the base class.
Decoupling Abilities via Composition
The fix isn't to find a way to inherit from more classes; it's to stop asking "what is this object?" and start asking "what can this object do?". Instead of saying a Paladin is a Warrior, we say a Character has a combat style and has a magic ability.
I prefer to extract these behaviors into interfaces and separate classes. This is composition. Here is how I would refactor that mess:
public interface AttackBehavior {
void executeAttack(String name);
}
public interface MagicBehavior {
void executeSpell(String name);
}
// Specific implementations
public class SwordAttack implements AttackBehavior {
public void executeAttack(String name) { System.out.println(name + " swings a sword!"); }
}
public class FireballSpell implements MagicBehavior {
public void executeSpell(String name) { System.out.println(name + " casts Fireball!"); }
}
public class HolySpell implements MagicBehavior {
public void executeSpell(String name) { System.out.println(name + " casts Holy Light!"); }
}
public class GameCharacter {
public String name;
private AttackBehavior attackBehavior;
private MagicBehavior magicBehavior; // Can be null if they can't cast
public GameCharacter(String name, AttackBehavior attack, MagicBehavior magic) {
this.name = name;
this.attackBehavior = attack;
this.magicBehavior = magic;
}
public void attack() {
attackBehavior.executeAttack(name);
}
public void cast() {
if (magicBehavior != null) {
magicBehavior.executeSpell(name);
} else {
System.out.println(name + " can't cast spells!");
}
}
}
Why this actually works in the long run
Notice how much more flexible this is. If we want to create a Paladin, we don't need a new class. We just instantiate a GameCharacter and pass in a SwordAttack and a HolySpell. If we want a Mage, we pass in a StaffAttack and a FireballSpell.
But the real superpower here is runtime flexibility. With inheritance, a Warrior is a Warrior until the program ends. With composition, I can change the AttackBehavior on the fly. If your character picks up a bow, you just call setAttackBehavior(new BowAttack()). You can't "change your parent class" at runtime, but you can absolutely change your components.
📋 Practical Task
Build a Dynamic Weapon Switching System
You are tasked with implementing a weapon system for a character that can switch between different attack modes during a game. Instead of creating separate classes for Archer, Swordsman, and Brawler, you will use composition.
Requirements:
- Create an interface
Weaponwith a methodString useWeapon(). - Implement three versions of
Weapon:Sword(returns "Slashes with a sword!"),Bow(returns "Shoots an arrow!"), andFists(returns "Punches with fists!"). - Create a
Playerclass that:- Holds a reference to a
Weaponobject. - Has a method
setWeapon(Weapon newWeapon)to change the equipped weapon. - Has a method
performAttack()that calls the current weapon'suseWeapon()method and prints the result.
- Holds a reference to a
- In your
mainmethod, instantiate aPlayer, make them attack with aSword, switch to aBow, and then attack again.
There are no comments for now.