Skip to main content

Relationships πŸ“„

In Object-Oriented Programming (OOP), relationships define how classes and objects interact with each other. There are several fundamental types of relationships in OOP:

Inheritance (IS-A Relationship)​

Inheritance represents an is-a relationship. A subclass inherits properties and behaviors (methods) from a parent class.

class Vehicle {
void start() {
System.out.println("Vehicle started");
}
}

class Car extends Vehicle {
void honk() {
System.out.println("Car honks");
}
}
  • Car is a Vehicle, so it inherits the start() method.
  • You can call Car().start() because of inheritance.
  • This promotes code reuse.

Use it when:

  • There is a true hierarchy.
  • The child class should inherit behavior from the parent.
  • The subclass can replace the parent without breaking logic (Liskov Substitution Principle).
  • Share common field, constructor.
  • Child class is type of parent.
  • Answer the question Who?

Composition (HAS-A Relationship)​

Composition represents a has-a relationship. One class contains another class as a part.

class Engine {
void start() {
System.out.println("Engine started");
}
}

class Car {
private Engine engine;

Car() { // constructor
engine = new Engine(); // Car has an Engine
}

void start() {
engine.start();
System.out.println("Car started");
}
}

  • Car has an Engine.
  • Engine exists as part of Car, showing strong ownership.
  • When Car is destroyed, Engine is usually destroyed too.

Use it when:

  • One object cannot exist meaningfully without the other.
  • The contained object is created and destroyed with the parent.
  • There is strong ownership.
  • Answer the question what, where

Aggregation (HAS-A Relationship, weaker than composition)​

Aggregation is also a has-a relationship but weaker. The contained object can exist independently of the parent.

class Department {
String name;

Department(String name) {
this.name = name;
}
}

class University {
ArrayList<Department> departments;

University() {
departments = new ArrayList<>(); // University has departments
}

void addDepartment(Department dept) {
departments.add(dept);
}
}
  • University has Departments.
  • Departments can exist even if the University object is destroyed.
  • This is loose coupling.

Use it when:

  • The child objects can exist independently.
  • Multiple parents may share the same object.

Association (General Relationship)​

Association is a general connection between classes. Neither class owns the other necessarily.

class Teacher {
String name;

Teacher(String name) {
this.name = name;
}
}

class Student {
String name;

Student(String name) {
this.name = name;
}

void assignTeacher(Teacher teacher) {
System.out.println(this.name + " is assigned to " + teacher.name);
}
}

  • Student and Teacher are related via assignment.
  • Both can exist independently.
  • Association is the most general relationship.

Use it when:

  • Classes communicate but do not own each other.
  • Relationship is loose.

Dependency (Uses-A Relationship)​

Dependency occurs when a class uses another class temporarily.

class Printer {
void printDocument(String doc) {
System.out.println("Printing: " + doc);
}
}

class Computer {
private Printer printer;

Computer() {
printer = new Printer(); // Computer has a Printer
}

void doPrint(String document) {
printer.printDocument(document);
}
}

  • Computer depends on Printer to print documents.
  • If Printer changes, Computer may need updates.
  • Dependency is temporary or short lived.

Use it when:

  • A class needs another class only inside a method.
  • The dependency is short-lived.

Can-Do Relationship (Ability / Interface)​

A Can-Do relationship represents what an object can do, usually expressed via interfaces, abstract classes, or protocols. It’s about capabilities, not ownership or hierarchy.

  • It’s often phrased as: β€œX can do Y”, e.g., a Bird can fly, a Car can honk.

  • The class implements behavior, but it may not inherit from a parent class.

Example(using abstract base classes):

interface Drivable {
void drive();
}

class Car implements Drivable {
@Override
public void drive() {
System.out.println("Car is driving");
}
}

class Truck implements Drivable {
@Override
public void drive() {
System.out.println("Truck is driving");
}
}
  • Car can drive, Truck can drive β†’ both implement the Drivable interface.
  • There’s no inheritance hierarchy between Car and Truckβ€”just a shared capability.
  • This allows polymorphism: any Drivable object can be asked to drive() without knowing its concrete type.
static void makeItDrive(Drivable vehicle) {
vehicle.drive();
}

Car car = new Car();
Truck truck = new Truck();

makeItDrive(car);
makeItDrive(truck);
  • Here, makeItDrive() works with any object that can fly, demonstrating flexible, decoupled design.

Use it when:

  • Many unrelated classes share a capability.
  • You want polymorphism.
  • Behavior should be plug-and-play.

Key Points about Can-Do:

AspectExplanation
Relation typeCapability / interface
Expressed asInterface, abstract class, protocol
ExampleBird can fly, Airplane can fly
AdvantageEnables polymorphism and flexible design
LifetimeIndependent; objects may or may not implement it

In short:

  • IS-A β†’ β€œCar is a Vehicle”
  • HAS-A β†’ β€œCar has Engine”
  • Can-Do β†’ β€œBird can fly”

Quick Summary Table​

RelationshipTypeExampleLifetime of objectNotes
InheritanceIS-ACar => VehicleSame as subclassCode reuse, polymorphism
CompositionHAS-ACar => EnginePart destroyed with wholeStrong ownership
AggregationHAS-AUniversity => DeptIndependentWeak ownership
AssociationGeneralStudent <=> TeacherIndependentGeneral relationship
DependencyUses-AComputer uses PrinterTemporaryShort-term usage
Can-DoCapabilityCar => DrivableIndependentBehavior via interface