Composition
Composition Over Inheritance
Composition is a design technique where objects contain other objects.
Basic Composition
// Engine is composed in Car
public class Engine {
int horsepower;
public void start() {
System.out.println("Engine started");
}
}
public class Car {
private Engine engine; // HAS-A relationship
public Car() {
this.engine = new Engine();
}
public void start() {
engine.start();
System.out.println("Car started");
}
}
Car car = new Car();
car.start();
// Output:
// Engine started
// Car started
Composition vs Inheritance
// Inheritance: IS-A relationship
public class Dog extends Animal { } // Dog IS-A Animal
// Composition: HAS-A relationship
public class Car {
private Engine engine; // Car HAS-A Engine
private Wheel[] wheels; // Car HAS-A Wheels
public Car(Engine engine) {
this.engine = engine;
this.wheels = new Wheel[4];
}
}
Benefits of Composition
// 1. Flexibility - can change behavior at runtime
public class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine; // Can swap engines!
}
}
// 2. Encapsulation - internal details hidden
public class OrderProcessor {
private PaymentGateway gateway;
private InventoryService inventory;
// Client doesn't need to know implementation details
}
// 3. Testability - easy to mock
public class UserService {
private UserRepository repository; // Can mock in tests
public UserService(UserRepository repository) {
this.repository = repository;
}
}
HAS-A Relationship
HAS-A Relationship
Objects contain other objects to share functionality.
Basic HAS-A
// Car HAS-A Engine
public class Car {
private Engine engine; // Composition
private List<Wheel> wheels; // Aggregation
public Car() {
this.engine = new Engine();
this.wheels = new ArrayList<>();
for (int i = 0; i < 4; i++) {
wheels.add(new Wheel());
}
}
}
Composition vs Aggregation
// Composition: lifecycle dependent
public class House {
private Room room; // Room created with House
public House() {
this.room = new Room(); // Room dies with House
}
}
// Aggregation: independent lifecycle
public class Team {
private List<Player> players; // Players exist independently
public Team(List<Player> players) {
this.players = players; // Players can exist without Team
}
}
Practical Examples
// University HAS-A Department HAS-A Course
public class University {
private List<Department> departments;
}
public class Department {
private List<Course> courses;
private List<Professor> professors;
}
// Computer HAS-A CPU HAS-A Core
public class Computer {
private CPU cpu;
private List<HardDrive> drives;
}
public class CPU {
private List<Core> cores;
}
Dependency Injection
// Inject dependencies through constructor
public class OrderService {
private final OrderRepository repository;
private final PaymentGateway gateway;
// Dependencies injected, not created internally
public OrderService(OrderRepository repo, PaymentGateway gateway) {
this.repository = repo;
this.gateway = gateway;
}
}
// Usage
OrderService service = new OrderService(
new DatabaseOrderRepository(),
new StripePaymentGateway()
);
SOLID Principles
SOLID Principles
Single Responsibility Principle (SRP)
// BAD: Multiple responsibilities
public class Employee {
public double calculateSalary() { }
public void saveToDatabase() { }
public String generateReport() { }
}
// GOOD: Single responsibility
public class Employee {
public double calculateSalary() { }
}
public class EmployeeRepository {
public void save(Employee emp) { }
}
public class ReportGenerator {
public String generate(Employee emp) { }
}
Open/Closed Principle (OCP)
// BAD: Must modify for new types
public class AreaCalculator {
public double calculate(Object shape) {
if (shape instanceof Circle) { }
else if (shape instanceof Rectangle) { }
}
}
// GOOD: Open for extension, closed for modification
public interface Shape {
double area();
}
public class Circle implements Shape {
public double area() { }
}
public class Rectangle implements Shape {
public double area() { }
}
// Add new shapes without modifying existing code!
Liskov Substitution Principle (LSP)
// BAD: Subclass breaks parent contract
public class Bird {
public void fly() { }
}
public class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException(); // Broken!
}
}
// GOOD: Proper substitution
public interface Flyable {
void fly();
}
public class Eagle implements Flyable {
public void fly() { }
}
public class Ostrich implements Walkable {
public void walk() { }
}
Interface Segregation Principle (ISP)
// BAD: Fat interface
public interface Worker {
void work();
void eat();
void sleep();
}
// GOOD: Segregated interfaces
public interface Workable {
void work();
}
public interface Feedable {
void eat();
}
public interface Sleepable {
void sleep();
}
public class Human implements Workable, Feedable, Sleepable { }
public class Robot implements Workable { } // Only what's needed
Dependency Inversion Principle (DIP)
// BAD: Depends on concrete class
public class OrderService {
private MySQLDatabase database; // Concrete dependency
public OrderService() {
this.database = new MySQLDatabase(); // Hard-coded
}
}
// GOOD: Depends on abstraction
public class OrderService {
private final Database database; // Abstraction
public OrderService(Database database) {
this.database = database; // Injected
}
}
When to Use Each
When to Use Composition vs Inheritance
Use Inheritance When:
// 1. Clear IS-A relationship
public class Dog extends Animal { } // Dog IS-A Animal
// 2. Shared behavior with specialization
public class ArrayList extends AbstractList { }
// 3. Framework requirement
public class MyServlet extends HttpServlet { }
Use Composition When:
// 1. HAS-A relationship
public class Car {
private Engine engine; // Car HAS-A Engine
}
// 2. Need flexibility to change behavior
public class OrderService {
private PaymentGateway gateway; // Can swap gateways
public void setGateway(PaymentGateway gateway) {
this.gateway = gateway;
}
}
// 3. Want to avoid inheritance complexity
public class Stack<E> {
private List<E> elements = new ArrayList<>(); // Composition
public void push(E item) { elements.add(item); }
public E pop() { return elements.remove(elements.size() - 1); }
}
Design Patterns Using Composition
// Strategy Pattern
public class ShoppingCart {
private PaymentStrategy paymentStrategy; // Composed
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
}
// Decorator Pattern
public class CoffeeShop {
private Coffee coffee; // Composed
public CoffeeShop addMilk() {
return new CoffeeShop(new MilkDecorator(coffee));
}
}
// Observer Pattern
public class EventEmitter {
private List<Listener> listeners; // Composed
public void subscribe(Listener listener) {
listeners.add(listener);
}
}
Decision Guide
// Ask yourself:
// 1. Is it IS-A? → Inheritance
// 2. Is it HAS-A? → Composition
// 3. Need flexibility? → Composition
// 4. Need multiple behaviors? → Composition
// 5. Framework requires it? → Inheritance
Practical Example
// BAD: Inheritance for code reuse
public class Stack extends ArrayList { }
// Problems: exposes add(), remove(), etc.
// GOOD: Composition for encapsulation
public class Stack<E> {
private final List<E> elements = new ArrayList<>();
public void push(E item) {
elements.add(item);
}
public E pop() {
return elements.remove(elements.size() - 1);
}
public boolean isEmpty() {
return elements.isEmpty();
}
}
Practice Problems
Design a music player with playlists and songs.
Example:
Input: createPlaylist(), addSong(), play()
Output: playing
Music player with composition
Optimal Solution — O(1) time, O(n) space
Use composition: Player HAS-A Playlist HAS-A Songs.
class MusicPlayer {
private Playlist currentPlaylist;
private List<Song> queue;
public void play() { currentPlaylist.play(); }
public void next() { currentPlaylist.next(); }
}
class Playlist {
private List<Song> songs;
private int currentIndex;
}Edge Cases:
- Empty playlist
- Single song
- Skip to end
Design an e-commerce system following SOLID principles.
Example:
Input: addToCart(), checkout(), processPayment()
Output: success
E-commerce with SOLID
Optimal Solution — O(1) time, O(n) space
Apply SOLID: separate concerns, use composition.
class OrderService {
private final CartRepository cartRepo;
private final PaymentService payment;
private final InventoryService inventory;
public OrderService(CartRepository cart, PaymentService payment, InventoryService inventory) {
this.cartRepo = cart;
this.payment = payment;
this.inventory = inventory;
}
}Edge Cases:
- Empty cart
- Payment failure
- Out of stock
Design a text editor with undo/redo functionality.
Example:
Input: type(), undo(), redo()
Output: text
Editor with command pattern
Optimal Solution — O(1) time, O(n) space
Use composition with Command pattern.
class TextEditor {
private StringBuilder content;
private Stack<Command> undoStack;
private Stack<Command> redoStack;
public void execute(Command cmd) {
cmd.execute();
undoStack.push(cmd);
redoStack.clear();
}
}Edge Cases:
- Undo empty
- Redo empty
- Multiple undos
Quiz
1. What is composition?
2. What does SOLID stand for?
3. When should you prefer composition over inheritance?
4. What is the Dependency Inversion Principle?
Flashcards
Question
What is the difference between composition and inheritance?
Click to reveal answer
Answer
Inheritance: IS-A (Dog IS-A Animal). Composition: HAS-A (Car HAS-A Engine). Use composition for flexibility.
Question
What does SRP stand for in SOLID?
Click to reveal answer
Answer
Single Responsibility Principle: A class should have only one reason to change.
Question
What is the Liskov Substitution Principle?
Click to reveal answer
Answer
Objects of a superclass should be replaceable with objects of a subclass without affecting correctness.
Question
When is composition preferred over inheritance?
Click to reveal answer
Answer
When you need flexibility, when HAS-A is more appropriate, or to avoid inheritance complexity.
Question
What is Composition & SOLID?
Click to reveal answer
Answer
Composition & SOLID is a key concept in Java programming.
Revision Notes
Key Takeaways
- 1.Composition over inheritance for flexibility
- 2.SOLID principles guide good design
- 3.HAS-A is composition, IS-A is inheritance
- 4.Dependency injection enables testability
Interview Tips
- •Know when to use composition vs inheritance
- •Understand all SOLID principles
- •Practice designing with composition
- •Know benefits of dependency injection
Cheat Sheet
Cheat Composition
- Composition: HAS-A (objects contain objects)
- Inheritance: IS-A (child extends parent)
- SOLID: Single, Open/Liskov, Interface, Dependency
- Prefer composition for flexibility
- Use inheritance for true IS-A