The Relationships of Classes in OOP

burhan sözer
7 min readOct 29, 2023

--

In this article, I aim to discuss the fundamental relationships between classes in object-oriented programming. To make the concepts more understandable, I have employed class diagrams and C# code examples. While the C# programming language provides concise solutions for these concepts, I will primarily focus on explaining the core ideas behind them.

When developing using OOP concepts, there are several key terms that developers must be familiar with. These terms include:

  • Generalization / Inheritance
  • Dependency
  • Aggregation
  • Composition
  • Abstraction
  • Interface
  • Polymorphism
  • Encapsulation

1- Generalization / Inheritance

Generalization is the practice of identifying shared attributes and behaviors among multiple classes and encapsulating them within a common superclass or parent/base class. This parent class includes the common properties and methods that are inherited by its subclasses.

There are two terms called Generalization and Inheritance:

  • Generalization is the term that we use to denote abstraction of common properties into a base class in UML.
  • When we implement Generalization in a programming language, it is called Inheritance.
  • So, Generalization and Inheritance are the same, the terminology just differs depending on the context where it is being used.

Generalization increases code reusability in software design, makes the code reusable, and helps design the code in a more organized way. At the same time, it enables a class to extend or modify the behavior of another class.

An example using the class diagram:

The Shape class is a base class
The shape class is the parent class of the point and rectangle classes.

The C# code of the class diagram:

class Shape
{
protected string style;
protected string shapeType;
public Shape(string style, string shapeType)
{
this.style = style;
this.shapeType = shapeType;
}
}

class Point : Shape
{
public Point(string style, string shapeType) : base(style, shapeType)
{
}

}

class Rectangle : Shape
{
public Rectangle(string style, string shapeType) : base(style, shapeType)
{
}
}

2- Dependency

It refers to a relationship in which one class needs to use or access another class. This relationship describes situations where one class depends on the properties or functionality of another class. Namely, a change in one thing may affect another.

It enables them to split classes into smaller, more specific pieces. This encourages designing according to a design principle where each class has a single responsibility (single responsibility principle).

An example using the class diagram:

The polygon class has the dependency to the point class

The C# code of the class diagram:

class Point
{
// some properties and methods...
}

class Polygon
{
public string Symbol;

public void AddPoint(Point point)
{
// add point process...
}
}

3- Aggregation

Aggregation represents a type of relationship in which a class can contain other classes. The lifecycles of these classes are independent of each other, meaning the lifespan of one class does not affect the other. This structural relationship typically denotes a ‘whole/part’ relationship and is often expressed as a ‘has-a’ relationship, signifying that one class possesses other classes.

An example using the class diagram:

The polyline class uses the point class.

The C# code of the class diagram:

class Point
{
// some properties and methods...
}

class Polyline
{
public Point[] path;

// constructor
public Polyline() { }
}

4- Composition

Composition is a stronger version of aggregation. In composition, the parts are tightly connected to the whole. When you create the whole object, you must also create its parts. There’s a close relationship between the whole and its parts, and their lifecycles are interdependent. This means that one object cannot work on its own; it’s an essential part of another object, often described as an ‘is-a-part-of’ relationship.

An example using the class diagram:

The point class is used in the constructor member of the polygon class.

The C# code of the class diagram:

class Point
{
// some properties and methods...
}

class Polygon
{
// constructor
public Polygon(Point[] points) { }
}

5- Abstraction

The abstract class can be used to enable abstraction in OOP. It is designed to be extended by other classes, serving as a blueprint for the creation of new classes that share common attributes and behaviors. It is performed to be inherited by other classes, and it serves as a template for those subclasses.

Abstract classes provide a mechanism for sharing code among related classes. When multiple classes have common attributes and methods, these can be grouped in an abstract class, reducing code duplication.

It defines a structure that subclasses must adhere to. This helps maintain consistency in how related classes are designed and used.

It supports polymorphism, which means you can use instances of derived classes through a reference to the abstract class. This allows for more generic and flexible code. (Polymorphism explains the next topic…)

It can be extended to create new classes that inherit both the structure and behavior of the abstract class. This simplifies the process of adding new functionality to your code.

An example using the class diagram:

The rectangle class uses the absract methods coming from the shape class.

The C# code of the class diagram:

abstract class Shape
{
public abstract double GetArea();
public abstract string GetWkt();
}

class Rectangle : Shape
{
private int _width;
private int _height;
public override double GetArea()
{
return (2 * (_height + _width));
}

public override string GetWkt()
{
throw new NotImplementedException();
}
}

6- Interface

An interface is an abstract structure that defines the methods (functions) and behaviors that a class must implement. It provides a contract or agreement that specifies what methods and behaviors a class should have.

When a class implements an interface, it is required to provide concrete implementations for all the methods defined in that interface.

Interfaces support polymorphism, allowing objects of different classes that implement the same interface to be treated as instances of that interface type. (Polymorphism explains the next topic…)

It enables a class to implement multiple interfaces, promoting loose coupling. Loose coupling makes the code more modular and easier to maintain.

An example using the class diagram:

The circle class is implemented the IMoveable interface and inherited the shape class.

The C# code of the class diagram:

abstract class Shape
{
public abstract double GetArea();
public abstract string GetWkt();
}

interface IMoveable
{
void ChangeLocation(double center, double diameter);
}

class Circle : Shape, IMoveable
{
private double _center;
private double _diameter;

public void ChangeLocation(double center, double diameter)
{
this._center = center;
this._diameter = diameter;
}

public override double GetArea()
{
return 0; // Todo >> Formulize
}

public override string GetWkt()
{
throw new NotImplementedException();
}

7- Polymorphism

Polymorphism is the concept that allows objects of different classes to be treated as if they were instances of a common parent-class. It means that objects from various classes can be used interchangeably if they adhere to the same interface or have a common base class.

It promotes code reuse by allowing you to use the same code with different classes, reducing redundancy and enhancing maintainability.

It simplifies code, enabling generic treatment of objects. This eliminates the need for separate code for each specific class, allowing the creation of generic code that works with any object adhering to a specific contract (interface or base class).

Moreover, it promotes the important OOP principle of abstraction. It empowers you to abstract away the specifics of individual implementations and concentrate on the shared behaviors among different objects.

The C# code of the sample code above:

Shape circle = new Circle();
circle.GetArea();
//circle.ChangeLocation() // You cannot use this method !

In this sample, circle variable is referenced the Shape class, but created instance from the circle class. So that, this variable cannot use the ChangeLocation method coming from the circle class.

8- Encapsulation

Encapsulation involves enclosing an object’s internal data within the object and limiting direct external access to that data. This is often done by using private or protected fields within the class.

Public methods (getters and setters) are used to provide controlled and validated interactions with the object’s data. It allows hiding the internal details of an object’s data. This is important for maintaining data integrity and ensuring that the object’s state is not accidentally or maliciously modified.

An example using the class diagram:

The C# code of the sample code above:

class Point
{
private double _coordinate;
private string _symbol;
// public string Symbol
// {
// get
// {
// return this._symbol
// }
// set
// {
// this._symbol = value;
// }
// //}
public string GetSymbol(string symbol) { return _symbol; }
public double GetCoordinate(double coordinate) { return coordinate; }
public void SetCoordinate(double coordinate) { this._coordinate = coordinate; }
}

In the sample code, the encapsulation is enabled using methods to explain it, however properties are more useful to encapsulate the fields.

Thanks for reading.

--

--