Decoding Relationships Between Classes: A Comprehensive Exploration

Establishing relationships between classes is fundamental in object-oriented programming. Without having relationships among objects, we can’t able to model them to real-world entities. In this post, we will explore relationships between classes in detail and understand their differences, use cases, and benefits.

OOP and Relationships Between Classes

The main building blocks of OOP are

  • Objects
  • Relationships between objects

In this post, our focus will be on relationships among objects. The most commonly used relationships are

  • IS-A (Inheritance)
  • HAS-A (Association)

Inheritance

  • Inheritance represents an “IS-A” relationship
  • It is a fundamental concept in OOP that enables a class to inherit properties and behaviours from another class.
  • Inheritance establishes a relationship between a base class (also known as a superclass, parent class) and a derived class (also known as a subclass, child class). The derived class can reuse the attributes and methods of the base class, allowing for code reuse and the creation of specialized classes.

Example of Inheritance

  • Dog “IS-A” type of Animal: Dog inherit few properties and behaviours from animal plus it has their own properties and behaviours.
  • Car “IS-A” type of vehicle
  • Rose “IS-A” type of flower

Python Code Example :

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

In this example, both Dog and Cat classes inherit the speak() method from the Animal class.

Benefits of Inheritance:

  • Code Reusability: Inherited properties and methods can be reused in the derived class, reducing redundancy and promoting efficient code development.
  • Polymorphism: Inheritance allows for polymorphism, where objects of different classes can be treated as objects of the same base class, enabling dynamic method binding and flexibility in the code.
  • Easy Maintenance: Changes made to the base class are automatically reflected in the derived classes, simplifying maintenance and updates.

Association

  • Its relationship among objects.
  • The association represents a “has-a” relationship, where one class has an instance of another class.
  • We can say one class stores the objects of another class as its instance variable.

Types of Association:

One-to-One:

  • A class is associated with exactly one instance of another class.
  • Consider two classes: Person and Passport. Each person can have only one passport, and each passport belongs to only one person.

One-to-Many

  • A class is associated with multiple instances of another class.
  • Consider the class Library and Books. A library can have multiple books, but each book belongs to only one library.

Many-to-One:

  • Multiple classes are associated with a single instance of another class.
  • Consider the class Teacher and Classroom. Multiple teachers can teach in the same classroom, but each classroom has only one teacher at a time.

Many-to-Many

  • Multiple instances of one class are associated with multiple instances of another class.
  • Consider class Student and Course. Each student can enrol in multiple courses, and each course can have multiple students.

Benefits of Association:

  • Modularity: Classes can be developed independently, enhancing modularity and maintainability of the code.
  • Flexibility: Changes in one class do not necessarily affect other classes, promoting flexibility and scalability.
  • Code Organization: Classes can be organized and structured based on their relationships, improving code readability and understanding.

Forms of association

  • Composition
  • Aggregation
association

The distinction between composition and aggregation lies in the strength of the relationship and the independence of the lifecycle between the classes involved.

Composition

  • One class is composed of one or more other classes.
  • The composition is a part of aggregation
  • The composed classes are part of the whole object and share the same lifecycle.
  • The composition represents a stronger form of association.
  • Composed objects are entirely dependent on the parent object, and they cannot exist independently.
  • Composed objects are created and destroyed together with the parent object.
  • Typically One-to-One or One-to-Many. Many-to-one and Many-to-Many are rare.
  • “Has-A” relationship, “Belongs-To” relationship or “part-of” relationship

Let’s clarify how composition can be considered both a “has-a” relationship and a “part-of” relationship:

class Car:
  def __init__(self):
      self.engine = Engine() 
      self.wheels = Wheels() 
      self.seats  = Seats() 

Has-A Relationship:

  • Composition represents a “has-a” relationship because a class can contain objects of other classes as its members or attributes. For example, a car class can have attributes like an engine, wheels, and seats. Each of these attributes is an object of another class, and the car “has” these components.
  • In this example, the `Car` class has attributes that are objects of other classes, indicating a “has-a” relationship through composition.

Part-of Relationship:

  • Composition also represents a “part-of” relationship because the objects of one class are essential parts of another class. The components are intimately related to the whole and are considered integral parts of the containing class.
  • `Engine`, `Wheels`, and `Seats` classes belong to the `Car` class.
  • `Engine`, `Wheels`, and `Seats` classes are considered parts of the `Car` class.
  • If the `Car` object is destroyed, its components are also typically destroyed, indicating a “part-of” relationship through composition.

So, the composition can be described as both a “has-a” relationship (because one class has objects of another class as its members) and a “part-of” relationship (because the objects of one class are integral parts of another class).

Example of Composition

  • A car consists of an engine. The engine and the car are tightly bound; the car cannot function without the engine.
  • A house is composed of various rooms such as bedrooms, living rooms, kitchens, etc. Each room is an integral part of the house, and the house is a composite object of these rooms.

Code example

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()  # Composition

Aggregation

  • In aggregation, one class has a reference to another class
  • The referenced class can exist independently.
  • Aggregation is a more loosely coupled relationship compared to composition, as the aggregated class is not entirely dependent on the containing class.
  •  It’s a weak association
  • A child is independent of its parent.

Example of Aggregation in Python:

class Department:
    def __init__(self, name):
        self.name = name

class University:
    def __init__(self, name, department):
        self.name = name
        self.department = department

math_department = Department("Mathematics")
my_university = University("Example University", math_department)

print(my_university.department.name)  # Output: Mathematics

In this example, the University class has an aggregation of the Department class, allowing it to represent the relationship between a university and its departments.

Advantages of object composition and aggregation over inheritance

  • Inheritance breaks encapsulation: By inheriting from a class you’re coupling the child class with a number of potential implementation details of the parent.
  • Composition is more flexible than inheritance: You can change the implementation of a class at run-time by changing the included object, thus changing its behaviour, but you can’t do this with inheritance, you can’t change the behaviour of the base class at run-time.
  • It is possible to implement “multiple inheritance” in languages that do not support it by composing multiple objects into one.
  • There is no conflict between methods/properties names, which might occur with inheritance.

The downside of composition and aggregation are:

  • The system’s behaviour may be harder to understand just by looking at the source code since it’s more dynamic and more interaction between classes happens in run-time, rather than compile time.

UML class diagrams

uml class diagram relationships

Aggregation

association

we are considering a car and a wheel example. A car cannot move without a wheel. But the wheel can be independently used with the bike, scooter, cycle, or any other vehicle. The wheel object can exist without the car object, which proves to be an aggregation relationship.

Composition

the composition association relationship connects the Person class with Brain class, Heart class, and Legs class. If the person is destroyed, the brain, heart, and legs will also get discarded.

composition

The order of strength from strong to weak is:

inheritance → implementation → composition → aggregation → association → dependency 

Generalization:

Generalization is a synonym of inheritance in the world of OOP. When a class is inherited from another class, then we can show this inheritance relationship with a simple arrow from the child class to the parent class.

Toyota Corolla and Ford Explorer are both popular cars in 2021. Since they are specific to Cars, they can be inherited from a generalization of Cars. Thus, they can be children of the general class Car.

Realization

Realization is also a type of inheritance but for interfaces. In a realization relationship, one entity (normally an interface) defines a set of functionalities as a contract and the other entity (normally a class) realizes the contract by implementing the functionality defined in the contract.

Dependency:

One class is dependent on another class.

Conclusion: Choosing the Right Approach

In summary, inheritance, composition, and aggregation are powerful tools in the world of object-oriented programming. Each concept offers unique benefits and use cases, allowing developers to design sophisticated systems while maintaining code readability, reusability, and flexibility. By understanding these concepts and choosing the right approach based on the specific requirements of a project, developers can create robust and maintainable software solutions.

As a developer, mastering these concepts will empower you to design elegant and efficient object-oriented systems, ensuring your codebase remains scalable and adaptable to future changes. Happy coding!

Resources

For further exploration, make sure to check out these helpful resources:

Leave a Comment