Skip to content
intermediatePhase 49 · Low-Level Design

Composition

Build complex objects by composing simpler ones.

30m
0 problems
Topic Progress0%

HAS-A Relationship

Composition models the HAS-A relationship where one class contains instances of other classes as members.

Composition vs Aggregation

Composition (Strong):            Aggregation (Weak):
- Part cannot exist without      - Part can exist independently
  the whole                       the whole
- Lifecycle managed by            - Lifecycle managed externally
  the whole                      
- Example: House HAS-A Room      - Example: Team HAS-A Player
  (Rooms destroyed with House)    (Players exist after Team disbands)

Composition in Code

// Composition: Order HAS-A PaymentMethod, HAS-A List<OrderItem>
public class Order {
    private final String id;
    private final List<OrderItem> items;  // Strong ownership
    private PaymentMethod paymentMethod;  // Strong ownership
    private OrderStatus status;
    
    public Order(String id, PaymentMethod payment) {
        this.id = id;
        this.items = new ArrayList<>();
        this.paymentMethod = payment;
        this.status = OrderStatus.PENDING;
    }
    
    public void addItem(OrderItem item) {
        items.add(item);  // Order owns items
    }
    
    public Money getTotal() {
        return items.stream()
            .map(OrderItem::getSubtotal)
            .reduce(Money.ZERO, Money::add);
    }
}

public class OrderItem {
    private final Product product;
    private final int quantity;
    
    public Money getSubtotal() {
        return product.getPrice().multiply(quantity);
    }
}

HAS-A Diagram

┌──────────────────────┐
│        Order          │
├──────────────────────┤
│ - id: String         │
│ - status: Status     │
├──────────────────────┤
│ + addItem()          │
│ + getTotal()         │
└──────────┬───────────┘
           │ 1
           │
           ▼ *
┌──────────────────────┐
│      OrderItem       │
├──────────────────────┤
│ - quantity: int      │
├──────────────────────┤
│ + getSubtotal()      │
└──────────┬───────────┘
           │ 1
           │
           ▼ 1
┌──────────────────────┐
│       Product        │
├──────────────────────┤
│ - name: String       │
│ - price: Money       │
├──────────────────────┤
│ + getPrice()         │
└──────────────────────┘

Design Rules

  1. Composition over inheritance: Prefer HAS-A over IS-A
  2. Single ownership: Each object has one owner
  3. Encapsulate children: Don't expose internal collections directly
  4. Delegate behavior: Let contained objects handle their own logic

Composition Patterns

Several patterns leverage composition to build flexible, maintainable systems.

1. Decorator Pattern

// Base interface
public interface DataSource {
    void writeData(String data);
    String readData();
}

// Core implementation
public class FileDataSource implements DataSource {
    private String filename;
    public void writeData(String data) { /* write to file */ }
    public String readData() { /* read from file */ }
}

// Decorator: adds compression
public class CompressionDecorator implements DataSource {
    private DataSource wrapped;
    
    public CompressionDecorator(DataSource source) {
        this.wrapped = source;
    }
    
    public void writeData(String data) {
        wrapped.writeData(compress(data));
    }
    
    public String readData() {
        return decompress(wrapped.readData());
    }
}

// Composable: file + compression + encryption
DataSource source = new EncryptionDecorator(
    new CompressionDecorator(
        new FileDataSource("data.txt")
    )
);

2. Strategy Pattern

public interface NavigationStrategy {
    Route calculateRoute(Location from, Location to);
}

public class DrivingStrategy implements NavigationStrategy {
    public Route calculateRoute(Location from, Location to) {
        // Driving algorithm
    }
}

public class WalkingStrategy implements NavigationStrategy {
    public Route calculateRoute(Location from, Location to) {
        // Walking algorithm
    }
}

// Context uses strategy via composition
public class Navigator {
    private NavigationStrategy strategy;
    
    public Navigator(NavigationStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(NavigationStrategy strategy) {
        this.strategy = strategy;
    }
    
    public Route findRoute(Location from, Location to) {
        return strategy.calculateRoute(from, to);
    }
}

3. Composite Pattern

// Uniform interface for individual and composite objects
public interface UIComponent {
    void render();
    int getPrice();
}

// Leaf: individual component
public class Button implements UIComponent {
    private String label;
    public void render() { /* render button */ }
    public int getPrice() { return 10; }
}

// Composite: contains other components
public class Panel implements UIComponent {
    private List<UIComponent> children = new ArrayList<>();
    
    public void add(UIComponent component) {
        children.add(component);
    }
    
    public void render() {
        children.forEach(UIComponent::render);
    }
    
    public int getPrice() {
        return children.stream()
            .mapToInt(UIComponent::getPrice)
            .sum();
    }
}

// Usage: tree of components
UIComponent form = new Panel();
form.add(new Button("Submit"));
form.add(new Button("Cancel"));
form.render(); // Renders all children

4. Chain of Responsibility

public abstract class Handler {
    protected Handler next;
    
    public Handler setNext(Handler next) {
        this.next = next;
        return next;
    }
    
    public abstract void handle(Request request);
}

// Chain: Auth → Validation → Processing
Handler chain = new AuthHandler();
chain.setNext(new ValidationHandler())
     .setNext(new ProcessingHandler());

chain.handle(request); // Processes through chain

Benefits

Composition provides significant advantages over inheritance for building maintainable systems.

Benefit Comparison

Inheritance                          Composition
──────────────                       ──────────────
Tight coupling                       Loose coupling
Static behavior                      Dynamic behavior
Hard to test (mock parent)           Easy to test (mock parts)
Deep hierarchies                    Flat structure
Fragile base class problem           No fragile base class
Cannot change at runtime             Change parts at runtime

1. Flexibility

// Can change behavior at runtime
Navigator navigator = new Navigator(new DrivingStrategy());
navigator.findRoute(from, to);  // Driving route

navigator.setStrategy(new WalkingStrategy());
navigator.findRoute(from, to);  // Walking route

2. Testability

// Easy to mock dependencies
class OrderServiceTest {
    @Test
    void testCreateOrder() {
        // Mock dependencies
        PaymentProcessor mockPayment = mock(PaymentProcessor.class);
        InventoryService mockInventory = mock(InventoryService.class);
        
        // Inject mocks
        OrderService service = new OrderService(mockPayment, mockInventory);
        
        // Test in isolation
        when(mockPayment.processPayment(any())).thenReturn(success);
        Order order = service.createOrder(cart);
        assertNotNull(order);
    }
}

3. Reusability

// Reusable components across different contexts
Logger logger = new ConsoleLogger();

// Same logger in different services
UserService userService = new UserService(logger);
OrderService orderService = new OrderService(logger);
PaymentService paymentService = new PaymentService(logger);

4. Single Responsibility

// Each component has one reason to change
class DataParser {
    // Only responsible for parsing
}

class DataValidator {
    // Only responsible for validation
}

class DataTransformer {
    // Only responsible for transformation
}

// Compose them together
DataPipeline pipeline = new DataPipeline(
    new DataParser(),
    new DataValidator(),
    new DataTransformer()
);

5. Avoiding Fragile Base Class

// Inheritance: changes in parent break children
public class ArrayList extends AbstractList {
    // If AbstractList changes behavior,
    // ArrayList might break unexpectedly
}

// Composition: changes in parts don't affect container
public class Playlist {
    private List<Song> songs = new ArrayList<>();
    // ArrayList changes don't affect Playlist
    // as long as List interface is honored
}

When Composition Shines

Scenario Why Composition Wins
Multiple behaviors needed Combine multiple objects
Behavior changes at runtime Swap strategies easily
Testing required Mock individual parts
Deep hierarchy forming Flatten with composition
Framework changes Parts are independent

The mantra: "Favor object composition over class inheritance" — Gang of Four

Practice Problems

0/3solved
Design Composition System

Design a scalable Composition system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Composition Scaling

How would you scale Composition to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Composition Failure Modes

Analyze potential failure modes for Composition and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What is the difference between composition and aggregation?

Question 1 options

2. Which pattern uses composition to add behavior dynamically?

Question 2 options

3. Why is composition better for testing?

Question 3 options

4. What is the Composite Pattern used for?

Question 4 options

5. What is the 'fragile base class' problem?

Question 5 options

Flashcards

Question

What is composition in OOP?

Answer

A HAS-A relationship where a class contains instances of other classes. Objects own and manage their parts. Preferred over inheritance for flexibility.

Question

What is the difference between composition and aggregation?

Answer

Composition: strong ownership, part depends on whole lifecycle. Aggregation: weak ownership, part can exist independently. House-Room (composition) vs Team-Player (aggregation).

Question

Name 4 composition patterns.

Answer

Decorator (add behavior), Strategy (swap algorithms), Composite (tree structures), Chain of Responsibility (pass requests through handlers).

Question

What is the fragile base class problem?

Answer

Changes in a parent class can unexpectedly break child classes due to tight coupling. Composition avoids this because parts are independent.

Question

What are 3 benefits of composition over inheritance?

Answer

1) Flexibility: change behavior at runtime. 2) Testability: easy to mock. 3) Loose coupling: parts are independent.

Revision Notes

Key Takeaways

  • 1.Composition models HAS-A relationships with strong or weak ownership
  • 2.Composition provides flexibility, testability, and loose coupling over inheritance
  • 3.Patterns like Decorator, Strategy, and Composite leverage composition
  • 4.Composition avoids the fragile base class problem
  • 5.Favor composition over class inheritance for most designs

Interview Tips

  • Show composition examples when discussing flexibility in your design
  • Explain how composition improves testability through dependency injection
  • Mention the fragile base class problem when justifying composition over inheritance
  • Demonstrate composition patterns like Decorator or Strategy in your design

Cheat Sheet

Composition - Cheat Sheet

HAS-A Relationship:

  • Order HAS-A PaymentMethod
  • Car HAS-A Engine
  • Panel HAS-A List

Composition vs Aggregation:

Composition Aggregation
Ownership Strong Weak
Lifecycle Managed by whole Independent
Example House-Room Team-Player

Composition Patterns:

  • Decorator: Add behavior dynamically
  • Strategy: Swap algorithms at runtime
  • Composite: Tree of uniform objects
  • Chain of Responsibility: Pass through handlers

Benefits:

  1. Flexibility (runtime changes)
  2. Testability (mock parts)
  3. Loose coupling (independent parts)
  4. Reusability (share parts)
  5. No fragile base class