Dependency, Inheritance, and Their Implementations

Learn about dependency, inheritance, and their implementations using a real-life example.

In this lesson, we’ll explore the concepts of dependency and inheritance relationships. We’ll look at clear explanations and examples to help solidify our understanding of these important object relationships. In the end, we’ll summarise all the relationships.

Dependency relationship

The dependency relationship represents a situation where one class, known as the dependent class, relies on another class, known as the dependency class, to perform a task or provide functionality. The dependent class utilizes the services or resources of the dependency class without having ownership or direct control over any of them. The dependency is typically expressed through method parameters, local variables, or interfaces.

Implementation of a real-life example

Let’s look at the following example:

Press + to interact
#include <iostream>
#include <string>
using namespace std;
class Logger {
public:
void log(const string& message) {
cout << "Log: " << message << endl;
}
};
int main() {
Logger logger;
logger.log("Dependency example");
return 0;
}
  • In this example, the Logger class depends on the std::cout object from the iostream library to output log messages. The log() method of the Logger class uses std::cout to write the log message to the console. Without the std::cout object, the Logger class would not be able to use this functionality.

  • The dependency relationship here is established through the usage of std::cout by the Logger class. The Logger class relies on the existence and proper functioning of std::cout to perform its logging operations. It demonstrates that the Logger class is dependent on the std::cout object, but there is no ownership or direct containment relationship between them.

In addition to the specific example of using std::cout, there are many other scenarios where dependency relationships occur in software development. One common example is when we write data to a file. We may rely on the functionality provided by the file writing library (e.g., fstream in C++) to open, write, and close the file. The file-writing library becomes a dependency for our program to perform file-related operations.

Moreover, in complex applications, we often utilize external libraries or frameworks that provide specialized functionality. For instance, if we’re working on an image processing application, we might depend on a third-party image processing library to handle tasks such as image filtering, resizing, or format conversion. In this case, our application relies on the library’s code and functionality to perform image-processing tasks effectively.

All such examples demonstrate dependency relationships where our code depends on external resources, libraries, or frameworks to accomplish certain tasks. These dependencies are crucial for the proper functioning of our programs but do not involve ownership, containment, or hierarchical relationships like composition or aggregation.

Inheritance relationship

Inheritance is a key relationship in object-oriented programming that facilitates code reuse and the establishment of hierarchical structures. It involves the creation of a subclass or derived class that inherits properties, methods, and behaviors from a superclass or base class. This relationship enables the subclass to extend or specialize the functionality of the superclass, resulting in an IS-A relationship. In real-life scenarios, inheritance can be observed in various contexts.

One common example is the biological hierarchy, where organisms are classified into phyla, classes, orders, and so on. Each level inherits characteristics from its parent level while introducing its own specific traits. This hierarchical arrangement allows for the classification and organization of diverse species based on shared attributes and evolutionary relationships.

In the realm of employee management, inheritance can be seen in the organizational hierarchy. Different roles, such as managers, supervisors, and employees, can be represented as subclasses of a more general employee superclass. Each subclass inherits common attributes and behaviors from the base class while adding specific functionalities tailored to their respective roles. This inheritance structure reflects the hierarchical nature of job positions within an organization.

In the context of shapes, inheritance is used to create a hierarchy of shapes, such as circles, rectangles, and triangles. A base class representing a generic shape can define common properties and methods shared by all shapes, while subclasses can inherit these attributes and introduce their own shape-specific characteristics. This inheritance relationship allows for code reusability and the ability to treat different shapes interchangeably.

The illustration below shows the inheritance relationship between the Rectangle and Triangle shapes and the Shape superclass.

Press + to interact
Inheritance relationship
Inheritance relationship

Similarly, in the context of software development, inheritance plays a crucial role in the creation of desktop applications using graphical user interfaces (GUIs). By leveraging the inheritance hierarchy of graphical user interface (GUI) components, developers can easily build their own applications by extending and customizing pre-existing classes and controls, leading to faster and more efficient development processes.

Inheritance and polymorphism

Polymorphism, in the context of inheritance, refers to the ability of objects of different derived classes to be treated as objects of their common base class. It allows us to write code that can work with objects of multiple derived classes interchangeably without the need for explicit type-checking. This concept is fundamental to object-oriented programming and plays a vital role in integrating data closer to hierarchy-based systems.

By utilizing polymorphism, we can create a hierarchy of classes where each derived class specializes in the behavior of the base class. This hierarchy represents a real-world relationship or concept, such as shapes or employees, where each subclass adds specific attributes or methods to the base class.

Polymorphism allows us to write code that operates on the base class, treating objects of different derived classes uniformly. This simplifies code maintenance and enhances flexibility, as we can add new derived classes without modifying the existing code that works with the base class. It also promotes code reuse and modularity, as we can create functions or algorithms that operate on the base class and can be applied to any derived class.

Overall, polymorphism in inheritance settings enables a more natural representation of hierarchical relationships in code, making it easier to model real-world systems and fostering code flexibility and reusability.

Let’s look at how the hierarchy-based inheritance and the polymorphic system work:

Press + to interact
#include <iostream>
using namespace std;
// Base class
class Shape {
protected:
int width;
int height;
public:
Shape(int w, int h) : width(w), height(h) {}
void display() {
cout << "Width: " << width << ", Height: " << height << endl;
}
virtual float getArea() = 0;
};
// Derived class
class Rectangle : public Shape {
public:
Rectangle(int w, int h) : Shape(w, h) {}
float getArea() {
return width * height;
}
};
// Derived class
class Triangle : public Shape {
public:
Triangle(int w, int h) : Shape(w, h) {}
float getArea() {
return 0.5 * width * height;
}
};
int main() {
// Create objects of derived classes
Shape* shapes[2];
shapes[0] = new Rectangle(5, 10);
shapes[1] = new Triangle(4, 6);
// Access the inherited member functions
for (int i = 0; i < 2; i++) {
shapes[i]->display(); // Calls the parent function
cout << "Area: "
<< shapes[i]->getArea() // Polymorphic behaviour, which function to call
<< endl; // decides on run time.
}
// Clean up the allocated memory
for (int i = 0; i < 2; i++) {
delete shapes[i];
}
return 0;
}

In this updated example, the main() function demonstrates polymorphism by using an array of pointers to the base class Shape. We create two objects, Rectangle and Triangle, and store them in the array. By using a pointer to the base class, we can access the inherited member functions display() and getArea() in a polymorphic manner. This means that the appropriate function implementations from the derived classes will be called at runtime based on the actual type of the object being pointed to.

Additionally, note that the attributes width and height and the function display() declared in the Shape class are reused in the derived classes (Rectangle and Triangle). This allows us to access and modify these attributes through the base class pointer, even though they are not explicitly declared in the derived classes. This demonstrates how inheritance enables the reuse of attributes and behaviors defined in the base class, providing a convenient and efficient way to build specialized classes while promoting code reuse and modularity.

The virtual keyword before the getArea() function in the Shape class indicates that this function is meant to be overridden in the derived classes. It enables dynamic dispatch or late binding, ensuring that the correct function implementation is called based on the actual type of the object rather than the pointer type. In the first iteration of the first for loop, it calls the getArea() function of Rectangle class, and in the second iteration, it calls the getArea() function of the Triangle. This is the essence of polymorphism in C++.

Summary

Object relationships can be summarized as follows:

  • Composition represents a strong relationship where one object is composed of other objects, and the lifetime of the composed objects is controlled by the container object.

  • Aggregation is a relationship where one object is composed of other objects, but the composed objects can exist independently.

  • Association denotes a relationship between objects, where they interact or are connected in some way without a strong ownership or containment.

  • Dependency signifies a relationship where one object depends on another object for its functionality, but there is no ownership or direct containment involved.

  • Inheritance represents an IS-A relationship, where one class inherits properties and behaviors from a superclass, allowing for code reuse and the creation of specialized subclasses.

Understanding and properly utilizing these relationships is crucial in designing effective and modular object-oriented systems.