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
- Composition over inheritance: Prefer HAS-A over IS-A
- Single ownership: Each object has one owner
- Encapsulate children: Don't expose internal collections directly
- 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
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 & reliabilityHow 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 decompositionAnalyze 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 degradationQuiz
1. What is the difference between composition and aggregation?
2. Which pattern uses composition to add behavior dynamically?
3. Why is composition better for testing?
4. What is the Composite Pattern used for?
5. What is the 'fragile base class' problem?
Flashcards
Question
What is composition in OOP?
Click to reveal answer
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?
Click to reveal answer
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.
Click to reveal answer
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?
Click to reveal answer
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?
Click to reveal answer
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:
- Flexibility (runtime changes)
- Testability (mock parts)
- Loose coupling (independent parts)
- Reusability (share parts)
- No fragile base class