Back

Activity 4.7 Composition: Car Has-an Engine

divider

Introduction

Activity 4.7

Composition (Has-A Relationships)

Topics

  • Composition: one class contains another class as a field
  • Object references as instance variables
  • Working safely with null
  • Delegation: calling methods on a contained object
  • AP-style sentinel return values

Composition: Has-A

Composition is a relationship where one object contains another object.

A Car has an Engine.

public class Car
{
private Engine engine; // Car has-an Engine (may be null)
public Car()
{
engine = null;
}
}

Why null Matters

Sometimes the contained object doesn’t exist yet. In that case, the reference is null.

  • No engine installed yet → engine == null
  • Calling methods on null causes a crash
  • Use a null check before using the contained object

Delegation

Delegation means one object calls a method on an object it contains. The Car can tell its Engine to start.

public void start()
{
if (engine != null)
{
engine.start();
}
}

AP-Style Sentinel Return Value

Sometimes a method needs to return a value even when data is missing. A common AP pattern is returning a sentinel value to represent “not available.”

public int getEngineHorsepower()
{
if (engine == null)
{
return -1; // sentinel value: no engine installed
}
return engine.getHorsepower();
}

Key Terms

Composition (Has-A)
A design where one class contains another object as a field.
Delegation
Calling a method on a contained object to perform work.
null
A reference that does not point to any object.
Sentinel Value
A special return value used to represent “missing” or “not available” data.

'F' → Fullscreen

Objectives

  • Implement a has-a relationship using instance variables
  • Safely handle null references in a composed class
  • Use delegation to call methods on a contained object
  • Use a sentinel return value to represent missing data
  • Build a small multi-file program using composition
divider

Activity Tasks

  • Create a new project named 4-7-Composition-Car-Engine.
  • Create three files: Engine.java, Car.java, and Program.java.
  • Complete each task in order.

Task 1: Engine Class

Implement the Engine class. It should track horsepower and whether it is running.

Engine.java
public class Engine
{
private int horsepower;
private boolean running;
public Engine(int horsepower)
{
this.horsepower = horsepower;
running = false;
}
public int getHorsepower()
{
return horsepower;
}
public boolean isRunning()
{
return running;
}
public void start()
{
running = true;
}
public void stop()
{
running = false;
}
}

Task 2: Car Class (Composition + Sentinel Value)

Implement the Car class that has-an Engine. The engine may be null if it has not been installed yet. Add the AP-style method getEngineHorsepower() that returns -1 when there is no engine.

Car.java
public class Car
{
private String make;
private String model;
private Engine engine; // Composition: Car has-an Engine (can be null)
public Car(String make, String model)
{
this.make = make;
this.model = model;
engine = null; // no engine installed yet
}
public String getMake()
{
return make;
}
public String getModel()
{
return model;
}
public void installEngine(Engine engine)
{
this.engine = engine;
}
public void removeEngine()
{
engine = null;
}
public boolean hasEngine()
{
return engine != null;
}
// AP-style sentinel value method
public int getEngineHorsepower()
{
if (engine == null)
{
return -1; // sentinel value: no engine installed
}
return engine.getHorsepower();
}
public void start()
{
if (engine == null)
{
System.out.println("Cannot start: no engine installed.");
return;
}
engine.start();
System.out.println("Car started.");
}
public void stop()
{
if (engine == null)
{
System.out.println("Cannot stop: no engine installed.");
return;
}
engine.stop();
System.out.println("Car stopped.");
}
public String getStatus()
{
int hp = getEngineHorsepower();
if (hp == -1)
{
return make + " " + model + " | Engine: none";
}
String state;
if (engine.isRunning())
{
state = "running";
}
else
{
state = "stopped";
}
return make + " " + model + " | Engine: " + hp + " HP (" + state + ")";
}
}

Task 3: Main Program

Write a small menu-driven program that allows a user to install/remove an engine, start/stop the car, and print horsepower using the sentinel-value method.

Program.java
import java.util.Scanner;
public class Program
{
public static void main(String[] args)
{
Scanner input = new Scanner(System.in);
Car car = new Car("Honda", "Civic");
Engine smallEngine = new Engine(140);
Engine sportEngine = new Engine(220);
System.out.println("-- Composition Demo: Car has-an Engine --");
System.out.println(car.getStatus());
boolean running = true;
while (running)
{
System.out.println("\n--- Menu ---");
System.out.println("1) Show status");
System.out.println("2) Install 140 HP engine");
System.out.println("3) Install 220 HP engine");
System.out.println("4) Remove engine");
System.out.println("5) Start car");
System.out.println("6) Stop car");
System.out.println("7) Print engine horsepower (AP-style)");
System.out.println("8) Quit");
System.out.print("> ");
String choice = input.nextLine();
if (choice.equals("1"))
{
System.out.println(car.getStatus());
}
else if (choice.equals("2"))
{
car.installEngine(smallEngine);
System.out.println("Installed 140 HP engine.");
System.out.println(car.getStatus());
}
else if (choice.equals("3"))
{
car.installEngine(sportEngine);
System.out.println("Installed 220 HP engine.");
System.out.println(car.getStatus());
}
else if (choice.equals("4"))
{
car.removeEngine();
System.out.println("Engine removed.");
System.out.println(car.getStatus());
}
else if (choice.equals("5"))
{
car.start();
System.out.println(car.getStatus());
}
else if (choice.equals("6"))
{
car.stop();
System.out.println(car.getStatus());
}
else if (choice.equals("7"))
{
int hp = car.getEngineHorsepower();
if (hp == -1)
{
System.out.println("Horsepower: (no engine installed)");
}
else
{
System.out.println("Horsepower: " + hp);
}
}
else if (choice.equals("8"))
{
running = false;
}
else
{
System.out.println("Invalid choice.");
}
}
System.out.println("\nGoodbye!");
}
}
divider

Sample Output

Your output should look similar to the sample below (values may vary slightly).

Sample Output
-- Composition Demo: Car has-an Engine --
Honda Civic | Engine: none
--- Menu ---
1) Show status
2) Install 140 HP engine
3) Install 220 HP engine
4) Remove engine
5) Start car
6) Stop car
7) Print engine horsepower (AP-style)
8) Quit
> 7
Horsepower: (no engine installed)
--- Menu ---
> 5
Cannot start: no engine installed.
Honda Civic | Engine: none
--- Menu ---
> 2
Installed 140 HP engine.
Honda Civic | Engine: 140 HP (stopped)
--- Menu ---
> 7
Horsepower: 140
--- Menu ---
> 5
Car started.
Honda Civic | Engine: 140 HP (running)
--- Menu ---
> 4
Engine removed.
Honda Civic | Engine: none
--- Menu ---
> 7
Horsepower: (no engine installed)
--- Menu ---
> 8
Goodbye!
divider

Reflection Questions

  1. In your own words, explain what it means for a Car to “have-an” Engine.
  2. Why might a Car’s engine be null in a program? Give a real-world reason.
  3. Identify one method in Car that uses delegation. What Engine method is being delegated to?
  4. What could go wrong if Car tried to call engine.start() without checking for null?
  5. Why does getEngineHorsepower() return -1 instead of returning 0? What does -1 represent?
divider

Submission

Submit your project files (Engine.java, Car.java, Program.java) and your reflection answers to the appropriate dropbox.

Activity Complete