Skip to content
intermediatePhase 11 · Java OOP

Composition & SOLID

Prefer composition over inheritance. Understand SOLID principles.

1h
3 problems
Topic Progress0%

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

0/3solved
Design Music Player
Composition

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 E-commerce System
SOLID

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 Text Editor
Composition

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?

Question 1 options

2. What does SOLID stand for?

Question 2 options

3. When should you prefer composition over inheritance?

Question 3 options

4. What is the Dependency Inversion Principle?

Question 4 options

Flashcards

Question

What is the difference between composition and inheritance?

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?

Answer

Single Responsibility Principle: A class should have only one reason to change.

Question

What is the Liskov Substitution Principle?

Answer

Objects of a superclass should be replaceable with objects of a subclass without affecting correctness.

Question

When is composition preferred over inheritance?

Answer

When you need flexibility, when HAS-A is more appropriate, or to avoid inheritance complexity.

Question

What is Composition & SOLID?

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