Skip to content

Session 5: Object Composition & Component Design

Week: 5
Element: ICTPRG430 Element 2.1
Duration: 4 hours
Phase: Object-Oriented Programming Theory


Session Introduction

In Session 4, you learned how to build classes with encapsulation. Now you'll learn how to build complex systems from simpler components using composition. This session teaches you the "has-a" relationship: a Car has an Engine, a Computer has a Processor, a Robot has a Battery. You'll understand when to use composition versus inheritance, and why composition often leads to more flexible, maintainable code. These universal patterns apply to any domain—including robotics.

Learning Objectives

By the end of this session, you will be able to:

  • Understand the "has-a" relationship and composition pattern
  • Design component classes that can be reused in different contexts
  • Build complex objects from simpler, focused components
  • Apply the "composition over inheritance" principle to system design
  • Explain when to use composition versus inheritance
  • Apply composition patterns to robotics software architecture

Session Structure

  1. The Composition Pattern - "Has-a" relationships and benefits
  2. Component Design Principles - Creating focused, reusable components
  3. Complete Example: Car System - Engine, Battery, GPS composition
  4. Complete Example: Computer System - Processor, Memory, Storage composition
  5. Composition vs Inheritance: Decision Guide - When to use each approach
  6. Robotics Application - Applying patterns to robot systems
  7. Pattern Application to Your Assessment - Applying composition to RobotBase

📊 Lecture Slides

Week 5 Presentation Slides

View the presentation slides for this session:

📊 Open Slides in New Window

The slides contain: - Overview of composition pattern and "has-a" relationships - Component design principles - Complete Car and Computer system examples - Composition vs Inheritance decision guide - Robotics application patterns - Assessment preparation and RobotBase refactoring


Pre-Session Preparation

Required

  • Python 3.9+ installed
  • VS Code with Python extension
  • Completed Session 4 (Class fundamentals)

Review

  • Session 4: Classes, objects, encapsulation
  • How to create instances of classes
  • Instance variables and methods

1. The Composition Pattern

What is Composition?

Composition is when one object contains other objects as components. The containing object has the component objects.

Car HAS-A Engine
Car HAS-A Battery  
Car HAS-A GPS
Computer HAS-A Processor
Computer HAS-A Memory
Robot HAS-A Battery
Robot HAS-A Sensor
Robot HAS-A Motor

This is called the "has-a" relationship—in contrast to inheritance's "is-a" relationship (which you'll learn in Session 6).

classDiagram
    class Car {
        -brand: str
        -model: str
        +start()
        +drive()
    }
    class Engine {
        -horsepower: int
        -is_running: bool
        +start()
        +stop()
    }
    class Battery {
        -capacity: int
        -level: int
        +drain()
        +charge()
    }
    class GPS {
        -model: str
        -location: tuple
        +update_location()
    }

    Car *-- Engine : has-a
    Car *-- Battery : has-a
    Car *-- GPS : has-a

    note for Car "Composition: Car contains\nthese components"

Why Use Composition?

Flexibility: - Swap components easily (upgrade engine without rebuilding car) - Change behavior at runtime (switch GPS providers) - Mix and match components for different configurations

Modularity: - Each component is independent - Test components in isolation - Reuse components in different systems

Clarity: - "Car has an Engine" is clearer than "Car is an EngineContainer" - Models real-world relationships naturally

Basic Pattern

class Component:
    """A reusable component."""
    def __init__(self, spec):
        self.spec = spec

    def operate(self):
        return f"{self.spec} operating"

class System:
    """A system that HAS-A component."""
    def __init__(self):
        # Composition: System contains a Component instance
        self.component = Component("v2.0")

    def use_component(self):
        # Delegate to the component
        return self.component.operate()

# Usage
system = System()
print(system.use_component())  # "v2.0 operating"

2. Component Design Principles

Good Components Are:

1. Focused - Do one thing well

class Battery:
    """Only manages battery state."""
    def __init__(self, capacity):
        self.capacity = capacity
        self.level = capacity

    def drain(self, amount):
        self.level = max(0, self.level - amount)

    def charge(self):
        self.level = self.capacity

    def get_percentage(self):
        return (self.level / self.capacity) * 100

2. Independent - Work without tight coupling

# ✅ Good: Battery works standalone
battery = Battery(100)
battery.drain(20)
print(battery.get_percentage())  # 80.0

# No dependencies on other classes!

3. Reusable - Can be used in multiple systems

# Same Battery class works for:
car_battery = Battery(500)
phone_battery = Battery(50)
robot_battery = Battery(100)

4. Testable - Easy to verify behavior

def test_battery_drain():
    battery = Battery(100)
    battery.drain(30)
    assert battery.level == 70  # Simple to test!

3. Complete Example: Car System

Let's build a Car system using composition. We'll create component classes, then assemble them into a Car.

Step 1: Engine Component

class Engine:
    """Engine component - manages power generation."""

    def __init__(self, horsepower):
        self.horsepower = horsepower
        self.is_running = False

    def start(self):
        """Start the engine."""
        self.is_running = True
        print(f"Engine started ({self.horsepower}hp)")

    def stop(self):
        """Stop the engine."""
        self.is_running = False
        print("Engine stopped")

    def get_power(self):
        """Get current power output.
"""
        return self.horsepower if self.is_running else 0

    def __str__(self):
        status = "running" if self.is_running else "stopped"
        return f"Engine({self.horsepower}hp, {status})"

Step 2: Battery Component

class Battery:
    """Battery component - manages energy storage."""

    def __init__(self, capacity):
        self.capacity = capacity
        self.level = capacity

    def drain(self, amount):
        """Drain battery by amount."""
        self.level = max(0, self.level - amount)
        if self.level == 0:
            print("⚠️  Battery depleted!")

    def charge(self, amount):
        """Charge battery by amount."""
        self.level = min(self.capacity, self.level + amount)

    def get_percentage(self):
        """Get battery level as percentage."""
        return (self.level / self.capacity) * 100

    def __str__(self):
        return f"Battery({self.get_percentage():.1f}%)"

Step 3: GPS Component

class GPS:
    """GPS component - manages navigation."""

    def __init__(self, model):
        self.model = model
        self.current_location = (0.0, 0.0)

    def update_location(self, latitude, longitude):
        """Update current GPS position."""
        self.current_location = (latitude, longitude)
        print(f"GPS: Location updated to {self.current_location}")

    def get_location(self):
        """Get current GPS position."""
        return self.current_location

    def __str__(self):
        return f"GPS({self.model}, location={self.current_location})"

Step 4: Car Using Composition

Now we compose all components into a Car:

class Car:
    """Car composed of Engine, Battery, and GPS components."""

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
        # Composition: Car HAS-A Engine
        self.engine = Engine(200)
        # Composition: Car HAS-A Battery
        self.battery = Battery(500)
        # Composition: Car HAS-A GPS
        self.gps = GPS("NavSystem 3000")

    def start(self):
        """Start the car - coordinates multiple components."""
        if self.battery.get_percentage() < 10:
            print("❌ Cannot start - battery too low")
            return False

        self.engine.start()
        self.battery.drain(5)  # Starting drains battery
        print(f"✅ {self.brand} {self.model} started")
        return True

    def drive(self, distance):
        """Drive the car - uses engine and battery."""
        if not self.engine.is_running:
            print("❌ Cannot drive - engine not running")
            return

        # Calculate battery drain based on distance
        drain_amount = distance * 0.5
        self.battery.drain(drain_amount)
        print(f"🚗 Drove {distance} km")

    def navigate_to(self, latitude, longitude):
        """Navigate to location - uses GPS."""
        self.gps.update_location(latitude, longitude)
        print(f"🧭 Navigating to {self.gps.get_location()}")

    def status_report(self):
        """Report status of all components."""
        print(f"\n=== {self.brand} {self.model} Status ===")
        print(f"Engine: {self.engine}")
        print(f"Battery: {self.battery}")
        print(f"GPS: {self.gps}")
        print("=" * 40)

    def __str__(self):
        return f"Car({self.brand} {self.model})"

Step 5: Using the Car System

# Create a car (automatically creates all components)
tesla = Car("Tesla", "Model S")

# Use the car
tesla.start()                    # Coordinates engine and battery
tesla.drive(50)                  # Uses engine and battery
tesla.navigate_to(37.7749, -122.4194)  # Uses GPS
tesla.status_report()            # Reports all component states

# Components work independently too!
print(f"\nBattery remaining: {tesla.battery.get_percentage():.1f}%")
print(f"Engine power: {tesla.engine.get_power()} hp")
print(f"Current location: {tesla.gps.get_location()}")

Output:

Engine started (200hp)
✅ Tesla Model S started
🚗 Drove 50 km
GPS: Location updated to (37.7749, -122.4194)
🧭 Navigating to (37.7749, -122.4194)

=== Tesla Model S Status ===
Engine: Engine(200hp, running)
Battery: Battery(94.0%)
GPS: GPS(NavSystem 3000, location=(37.7749, -122.4194))
========================================

Battery remaining: 94.0%
Engine power: 200 hp
Current location: (37.7749, -122.4194)
flowchart TD
    subgraph "Car System - Composition in Action"
        Car[Car<br/>Tesla Model S]
        Car -->|delegates to| Engine[Engine<br/>200hp]
        Car -->|delegates to| Battery[Battery<br/>500 capacity]
        Car -->|delegates to| GPS[GPS<br/>NavSystem 3000]

        Start[start method] -.->|checks| Battery
        Start -.->|calls| Engine
        Drive[drive method] -.->|uses| Engine
        Drive -.->|drains| Battery
        Navigate[navigate_to] -.->|updates| GPS
    end

    style Car fill:#4CAF50,color:#fff
    style Engine fill:#2196F3,color:#fff
    style Battery fill:#FF9800,color:#fff
    style GPS fill:#9C27B0,color:#fff

Key Observations

  1. Car delegates to components: tesla.drive() internally calls battery.drain()
  2. Components are independent: Each component can be tested separately
  3. Components are reusable: Same Battery class could be used in phone, laptop, robot
  4. Easy to modify: To upgrade GPS, change GPS class—Car code unchanged
  5. Clear responsibilities: Engine handles power, Battery handles energy, GPS handles navigation

4. Complete Example: Computer System

Let's see composition in a different domain to reinforce the pattern.

Component Classes

class Processor:
    """CPU component - handles computation."""

    def __init__(self, model, cores, ghz):
        self.model = model
        self.cores = cores
        self.ghz = ghz
        self.load = 0  # Current CPU load percentage

    def process_task(self, complexity):
        """Process a task (increases load temporarily)."""
        self.load = min(100, complexity)
        print(f"⚙️  Processing task (load: {self.load}%)")

    def idle(self):
        """Return to idle state."""
        self.load = 0

    def __str__(self):
        return f"Processor({self.model}, {self.cores} cores @ {self.ghz}GHz, load: {self.load}%)"


class Memory:
    """RAM component - handles data storage."""

    def __init__(self, capacity_gb):
        self.capacity = capacity_gb * 1024  # Convert to MB
        self.used = 0

    def allocate(self, amount_mb):
        """Allocate memory."""
        if self.used + amount_mb > self.capacity:
            print(f"❌ Out of memory! Used: {self.used}MB, Tried to allocate: {amount_mb}MB")
            return False
        self.used += amount_mb
        print(f"📝 Allocated {amount_mb}MB")
        return True

    def free(self, amount_mb):
        """Free memory."""
        self.used = max(0, self.used - amount_mb)
        print(f"🗑️  Freed {amount_mb}MB")

    def get_percentage_used(self):
        return (self.used / self.capacity) * 100

    def __str__(self):
        return f"Memory({self.used}MB / {self.capacity}MB used, {self.get_percentage_used():.1f}%)"


class Storage:
    """Hard drive component - handles persistent storage."""

    def __init__(self, capacity_gb, storage_type):
        self.capacity = capacity_gb
        self.used = 0
        self.storage_type = storage_type  # "SSD" or "HDD"

    def write(self, data_gb):
        """Write data to storage."""
        if self.used + data_gb > self.capacity:
            print("❌ Storage full!")
            return False
        self.used += data_gb
        speed = "fast" if self.storage_type == "SSD" else "slow"
        print(f"💾 Wrote {data_gb}GB ({speed})")
        return True

    def read(self, data_gb):
        """Read data from storage."""
        speed = "fast" if self.storage_type == "SSD" else "slow"
        print(f"📖 Read {data_gb}GB ({speed})")

    def __str__(self):
        return f"Storage({self.storage_type}, {self.used}GB / {self.capacity}GB used)"

Computer Using Composition

class Computer:
    """Computer composed of Processor, Memory, and Storage."""

    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
        # Composition: Computer HAS-A Processor
        self.processor = Processor("Intel i7", cores=8, ghz=3.6)
        # Composition: Computer HAS-A Memory
        self.memory = Memory(capacity_gb=16)
        # Composition: Computer HAS-A Storage
        self.storage = Storage(capacity_gb=512, storage_type="SSD")

    def run_program(self, name, memory_needed, storage_needed):
        """Run a program - coordinates all components."""
        print(f"\n🖥️  Running {name}...")

        # Check and allocate memory
        if not self.memory.allocate(memory_needed):
            return False

        # Process the task
        self.processor.process_task(complexity=75)

        # Write output to storage
        if not self.storage.write(storage_needed):
            self.memory.free(memory_needed)
            return False

        print(f"✅ {name} completed successfully")
        self.processor.idle()
        return True

    def system_report(self):
        """Report all component states."""
        print(f"\n=== {self.brand} {self.model} System Report ===")
        print(f"CPU: {self.processor}")
        print(f"RAM: {self.memory}")
        print(f"Disk: {self.storage}")
        print("=" * 50)

    def __str__(self):
        return f"Computer({self.brand} {self.model})"


# Usage
laptop = Computer("Dell", "XPS 15")
laptop.run_program("Video Editor", memory_needed=4096, storage_needed=50)
laptop.run_program("Code Compiler", memory_needed=2048, storage_needed=1)
laptop.system_report()

Output:

🖥️  Running Video Editor...
📝 Allocated 4096MB
⚙️  Processing task (load: 75%)
💾 Wrote 50GB (fast)
✅ Video Editor completed successfully

🖥️  Running Code Compiler...
📝 Allocated 2048MB
⚙️  Processing task (load: 75%)
💾 Wrote 1GB (fast)
✅ Code Compiler completed successfully

=== Dell XPS 15 System Report ===
CPU: Processor(Intel i7, 8 cores @ 3.6GHz, load: 0%)
RAM: Memory(6144MB / 16384MB used, 37.5%)
Disk: Storage(SSD, 51GB / 512GB used)
==================================================

5. Composition vs Inheritance: Decision Guide

When to Use Composition ("has-a")

Use composition when:

  • Components can be reused in different contexts
  • You need flexibility to swap components at runtime
  • Relationship is "uses" or "contains"
  • You want loose coupling
  • Components have independent lifecycles

Examples: - Car HAS-A Engine (engine could work without car for testing) - Robot HAS-A Battery (battery is a separate component) - Computer HAS-A Processor (processor could be upgraded)

When to Use Inheritance ("is-a")

Use inheritance when:

  • Clear "is a specialized type of" relationship
  • Subclass should support polymorphism
  • Shared behavior and interface make sense
  • Tight coupling is appropriate

Examples: - LineFollowerRobot IS-A Robot (specialized robot type) - SavingsAccount IS-A BankAccount (specialized account type) - ElectricCar IS-A Car (specialized car type)

Side-by-Side Comparison

classDiagram
    %% Composition Example (Good)
    class CarComposition["Car (Composition)"] {
        -engine: Engine
        -battery: Battery
        +start()
        +drive()
    }
    class Engine {
        -horsepower: int
        +start()
        +stop()
    }
    class Battery {
        -capacity: int
        +drain()
        +charge()
    }

    CarComposition o-- Engine : HAS-A (composition)
    CarComposition o-- Battery : HAS-A (composition)

    %% Inheritance Example (comparison)
    class Vehicle {
        +start()
        +stop()
    }
    class ElectricCar {
        +charge()
    }
    class GasCar {
        +refuel()
    }

    Vehicle <|-- ElectricCar : IS-A (inheritance)
    Vehicle <|-- GasCar : IS-A (inheritance)

    note for CarComposition "Flexible: Can swap\ncomponents at runtime"
    note for Vehicle "Rigid: Inheritance\nhierarchy is fixed"
# ❌ Poor use of inheritance
class Engine:
    def start(self):
        print("Engine started")

class Car(Engine):  # Car IS-A Engine? No!
    pass

# ✅ Good use of composition
class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()  # Car HAS-A Engine ✓

    def start(self):
        self.engine.start()

The Famous Example

# Classic design mistake
class Bird:
    def fly(self):
        print("Flying")

class Penguin(Bird):  # Penguin IS-A Bird
    def fly(self):
        raise Exception("Penguins can't fly!")  # ❌ Problem!

# Better design with composition
class FlyingAbility:
    def fly(self):
        print("Flying")

class Bird:
    def __init__(self, can_fly=True):
        # Composition: Bird HAS-A flying ability (or not)
        self.flying_ability = FlyingAbility() if can_fly else None

    def fly(self):
        if self.flying_ability:
            self.flying_ability.fly()
        else:
            print("This bird doesn't fly")

eagle = Bird(can_fly=True)
penguin = Bird(can_fly=False)

eagle.fly()    # "Flying"
penguin.fly()  # "This bird doesn't fly"
flowchart LR
    subgraph Problem["❌ Inheritance Problem"]
        Bird1[Bird<br/>+fly]
        Penguin1[Penguin<br/>+fly throws error]
        Bird1 --> Penguin1
        style Penguin1 fill:#f44336,color:#fff
    end

    subgraph Solution["✅ Composition Solution"]
        Bird2[Bird<br/>flying_ability: optional]
        FlyingAbility[FlyingAbility<br/>+fly]
        Eagle[Eagle<br/>with FlyingAbility]
        Penguin2[Penguin<br/>no FlyingAbility]

        Bird2 -.->|HAS-A| FlyingAbility
        Bird2 --> Eagle
        Bird2 --> Penguin2
        Eagle -.->|uses| FlyingAbility

        style Eagle fill:#4CAF50,color:#fff
        style Penguin2 fill:#4CAF50,color:#fff
    end

Rule of Thumb

"Favor composition over inheritance" is a fundamental design principle. When in doubt:

  1. Start with composition - It's more flexible
  2. Use inheritance sparingly - Only for clear "is-a" relationships
  3. Combine both - Inheritance for type hierarchy, composition for capabilities
# ✅ Good design combining both
class Robot:
    """Base robot - uses composition for components."""
    def __init__(self, name):
        self.name = name
        self.battery = Battery(100)  # Composition
        self.motor = Motor()         # Composition

class LineFollowerRobot(Robot):  # Inheritance
    """Specialized robot - IS-A Robot, HAS components."""
    def __init__(self, name):
        super().__init__(name)
        # Inherits composition from Robot
        # Adds specialized behavior
        self.line_sensor = LineSensor()  # Additional composition
classDiagram
    class Robot {
        -name: str
        -battery: Battery
        -motor: Motor
        +move()
        +check_battery()
    }
    class LineFollowerRobot {
        -line_sensor: LineSensor
        +follow_line()
    }
    class Battery {
        -capacity: int
        +drain()
        +charge()
    }
    class Motor {
        -speed: int
        +run()
        +stop()
    }
    class LineSensor {
        +read_line()
    }

    Robot *-- Battery : HAS-A (composition)
    Robot *-- Motor : HAS-A (composition)
    Robot <|-- LineFollowerRobot : IS-A (inheritance)
    LineFollowerRobot *-- LineSensor : HAS-A (composition)

    note for Robot "Base class uses\ncomposition for shared\ncomponents"
    note for LineFollowerRobot "Inherits Robot behavior\nAdds specialized components"

6. Robotics Application

How This Applies to Robots

Everything you've learned applies directly to robotics systems:

Robots are composed of components: - Battery (power management) - Motors (movement control) - Sensors (environmental awareness) - Computing unit (decision-making) - Communication system (connectivity)

Example component relationships in robotics:

Robot HAS-A Battery
Robot HAS-A Motor (or multiple motors)
Robot HAS-A Sensor (or sensor array)
Robot HAS-A IMU (Inertial Measurement Unit)
Robot HAS-A GPS
Robot HAS-A Camera

Benefits in Robotics

1. Modularity: Test each component independently

# Test battery drain calculation without full robot
battery = Battery(100)
battery.drain(20)
assert battery.get_percentage() == 80.0  

2. Flexibility: Swap components for different missions

# Same robot base, different sensor configurations
exploration_robot = Robot("Explorer")
exploration_robot.add_sensor(LidarSensor())
exploration_robot.add_sensor(CameraSensor())

rescue_robot = Robot("Rescuer")
rescue_robot.add_sensor(ThermalSensor())
rescue_robot.add_sensor(MicrophoneArray())

3. Reusability: Use same component in different robot types

# Same Battery class works for any robot
quadruped_robot.battery = Battery(200)
drone.battery = Battery(50)
rover.battery = Battery(150)

4. Testability: Verify components before integration

# Test motor control logic independently
motor = Motor(max_speed=10)
motor.set_speed(5)
assert motor.current_speed == 5  

Conceptual Example

Here's how composition patterns apply conceptually to your robot assessment:

# Conceptual structure (NOT implementation code!)
# This shows the PATTERN, you implement the specifics

class RobotComponent:
    """Base pattern for any robot component."""
    def get_status(self):
        # Components can report their state
        pass

class RobotSystem:
    """System composed of multiple components."""
    def __init__(self):
        # Robot HAS components
        self.components = []

    def add_component(self, component):
        # Add any component to the system
        self.components.append(component)

    def system_check(self):
        # Coordinate multiple components
        for component in self.components:
            component.get_status()

Think about: - What components does a robot have? - How do components interact? - Which data belongs to which component? - How does the main robot coordinate components?


7. Pattern Application to Your Assessment

Study the Examples, Apply the Patterns

Just like Session 4 taught OOP with BankAccount (and you applied patterns to RobotBase), this session teaches composition with Car/Computer examples. You must apply these patterns independently to your robot assessment.

Questions to Guide Your Design

  1. What are the components?
  2. What separate concerns exist in a robot system?
  3. Battery management? Sensor handling? Movement control?

  4. What should each component do?

  5. Single responsibility: What's the ONE job of each component?
  6. Independence: Can it work alone for testing?

  7. How do components interact?

  8. What data flows between components?
  9. How does the main robot coordinate them?

  10. What should be composed vs inherited?

  11. Does robot HAS-A battery or IS-A battery?
  12. Does robot HAS-A sensor or IS-A sensor?

Design Pattern: Same as Examples

# This is the PATTERN from Car and Computer
# Apply this conceptually to your robot

# Step 1: Identify components (like Engine, Battery, GPS)
# Step 2: Create focused component classes (like examples show)
# Step 3: Compose them in main system (like Car/Computer do)
# Step 4: Coordinate components in methods (like start(), drive())

What You Don't Get

  • No RobotBase composition implementation
  • No robot-specific component code
  • No complete robot system example

What You Do Get

  • Clear composition pattern (Car, Computer)
  • Complete working examples in other domains
  • Understanding of has-a relationships
  • Design principles to apply

Why This Matters

Seeing Car and Computer patterns teaches you composition. Building robot composition independently proves you understand it. This is learning patterns, not memorizing code.


Check Your Knowledge

Q1: Composition vs Inheritance

When should you use composition instead of inheritance?

Answer

Use composition when: - Components can be reused independently - You need flexibility to swap components - Relationship is "has-a" not "is-a" - Example: Car HAS-A Engine (composition) not Car IS-A Engine (wrong!)

Q2: Component Design

What makes a good component class?

Answer

Good components are: - Focused: Do one thing well (Single Responsibility) - Independent: Work without tight coupling to container - Reusable: Can be used in different systems - Testable: Easy to verify behavior independently

Q3: Real-World Application

In the Car example, what would happen if Engine was tightly coupled to Car?

Answer

Problems with tight coupling: - Can't test Engine independently - Can't reuse Engine in boat, generator, etc. - Changing Engine requires changing Car - Hard to swap engines (no flexibility)

Composition solves this by keeping Engine independent!

Q4: Robotics Context

How does composition apply to robotics?

Answer

Robots are composed of independent components: - Robot HAS-A Battery (power management) - Robot HAS-A Motor (movement) - Robot HAS-A Sensor (perception) - Each component can be tested, upgraded, or replaced independently - Same patterns as Car (HAS Engine) and Computer (HAS Processor)


Key Concepts Summary

Concept Description Example
Composition Object contains other objects as components Car HAS-A Engine
"Has-a" relationship Container has component Robot HAS-A Battery
Component Independent, reusable object Battery class works in car, phone, robot
Delegation Container forwards requests to components car.start() calls engine.start()
Flexibility Swap components at runtime Upgrade GPS without changing Car code
Modularity Components work independently Test Battery without full Car
Loose coupling Components don't depend on container Engine doesn't know about Car class
Reusability Use same component in multiple systems Battery in car, robot, laptop

Out-of-Class Activities

To reinforce your understanding of composition:

1. Study the Complete Examples

Run and modify the Car and Computer examples: - Change component specifications (bigger engine, more memory) - Add new components (GPS accuracy, storage speed) - See how changes to components don't break the main system

2. Design a Smart Home System

Create a composition-based Smart Home: - Components: Thermostat, SecuritySystem, LightController, Doorbell - System: SmartHome class that coordinates all components - Practice: Same pattern as Car and Computer

3. Compare Designs

Take a problem and solve it two ways: - With inheritance - With composition - Reflect: Which is more flexible? Which is clearer?

4. Apply to Your Assessment

Think about your RobotBase: - What components exist in a robot system? - How would composition make it more modular? - What can be tested independently? - How do components coordinate?


Resources

  • Session 4: Review class fundamentals before tackling composition
  • Session 6: Next week covers inheritance—you'll compare both approaches
  • Python Docs: Class composition examples
  • Design Patterns: "Composition over Inheritance" principle

Session Summary

Today you learned:

  1. Composition Pattern: "Has-a" relationships for building complex systems
  2. Component Design: Creating focused, reusable, independent components
  3. Practical Examples: Complete Car and Computer systems using composition
  4. Design Decisions: When to use composition vs inheritance
  5. Robotics Application: How these patterns apply to robot architecture

Next Session: Session 6 covers inheritance ("is-a" relationships) and polymorphism. You'll compare composition and inheritance approaches and learn when each is appropriate.

Your Task: Apply composition patterns from today's examples to enhance your robot system. Think about what components your robot should have and how they should interact.


Navigation:
← Session 4 | Learning Plan | Session 6 →


Assessment: RobotBase Refactoring Requirements

Now that you understand composition, your task is to refactor RobotBase to use component-based architecture.

RobotBase Component Architecture

classDiagram
    class RobotBase {
        -name: str
        -battery: BatteryComponent
        -motor: MotorComponent
        -sensor: SensorComponent
        +__init__(name)
        +move(distance)
        +scan_environment()
        +get_battery_status()
        +stop()
    }

    class BatteryComponent {
        -capacity: int
        -current_level: int
        +drain(amount)
        +charge()
        +get_percentage()
        +is_depleted()
    }

    class MotorComponent {
        -speed: int
        -is_running: bool
        +forward(distance)
        +backward(distance)
        +stop()
        +set_speed(speed)
    }

    class SensorComponent {
        -sensor_type: str
        +read_data()
        +detect_obstacle()
        +get_reading()
    }

    RobotBase *-- BatteryComponent : has-a
    RobotBase *-- MotorComponent : has-a
    RobotBase *-- SensorComponent : has-a

Requirements for Your RobotBase

Your RobotBase class should follow composition principles:

1. Component Initialization - Create Battery, Motor, and Sensor components in __init__ - Each component is independent and self-contained - Robot coordinates component interactions

2. Battery Component Responsibilities - Manage energy storage and drain - Track capacity and current level - Provide battery percentage - Detect low battery conditions

3. Motor Component Responsibilities - Control movement direction and speed - Track running state - Execute move commands - Handle stopping

4. Sensor Component Responsibilities - Read environmental data - Detect obstacles - Provide sensor readings - Be independent of robot's movement

5. RobotBase Delegation - move() method delegates to Motor and Battery - scan_environment() delegates to Sensor - get_battery_status() delegates to Battery - Robot methods coordinate components, don't duplicate their logic

6. Design Principles Applied - ✅ Focused: Each component has one responsibility - ✅ Independent: Components work without knowing about RobotBase - ✅ Reusable: Components can be used in other robot types - ✅ Testable: Test components separately from RobotBase - ✅ Loose Coupling: Changes to components don't break RobotBase

Key Differences from Session 4

In Session 4, you built RobotBase with encapsulation (getters/setters). Now you're adding composition to manage complexity:

Aspect Session 4 Session 5
Structure Single class with all methods Multiple component classes
Responsibility RobotBase handles everything Components specialize, RobotBase coordinates
Testing Test entire RobotBase Test components independently
Reusability Battery only in RobotBase Battery reusable in any robot
Flexibility Hard to swap implementations Easy to swap component implementations

Your Deliverable

Implement the three components (Battery, Motor, Sensor) and refactor RobotBase to use them. Your code should demonstrate:

  1. Clear component interfaces (what methods each component has)
  2. Independent component behavior (no tight coupling)
  3. RobotBase coordinating components through delegation
  4. Passing all unit tests with the new architecture

Navigation:
← Session 4 | Learning Plan | Session 6 →