Blog 3 min read

Two easy approaches to improve your C++ OOP design skills.

Share this article
Two easy approaches to improve your C++ OOP design skills.

Coupling is usually contrasted with cohesion. Low coupling often correlates with high cohesion, and vice versa. Low coupling is often a sign of a well-structured computer system and a good design, and when combined with high cohesion, supports the general goals of high readability and maintainability. The goal of this case study is to demonstrate the benefits of loose coupling and high cohesion, and how they can be achieved in C++. The case study involves designing an application that reads data from a file, processes it, and writes the result to an output file.

A poorly designed solution

In this first design, only one class, named CDataProcessor is used to:

  • Get data from a file.
  • Process the data.
  • Print the result.

And the main method invokes the methods of this class.

Class diagram c1

Drawbacks of this solution

  • Low cohesion: The CDataProcessor class has many responsibilities, so we can’t easily reuse the algorithm in other applications.
  • High coupling: The processing logic is tightly coupled to the console and the data provider.

Design refactoring

High cohesion

To improve cohesion, each responsibility should be assigned to a different class, so we need three classes:

  • CFileProvider: Gets data from a file.
  • CDataProcessing: Processes the data. This class can use other classes to perform the processing, but to keep the design simple, we consider it sufficient for our example.
  • CResultReporting: Writes the result to a file.

Each class now has a single responsibility. The advantages of this design are:

  • The classes are easier to understand.
  • The code is easier to maintain.
  • The classes are easier to reuse in other applications.

Low coupling

What happens if the data is stored in a database rather than a file? In our previous design, the application is tightly coupled to a file provider.

To solve this problem, we need an interface that provides methods for retrieving data from any source. For file-based data, we then need a class that implements this interface.

For this purpose, using NVI can be a good solution. This pattern is more useful than using only abstract classes because it’s possible to define a pre- and post-condition. It’s a useful object-oriented programming technique, particularly during development. Pre- and post-conditions ensure that invariants of a class hierarchy (and in general, an abstraction) are not violated at designated points during execution of a program.

In our case, we can add the IDataProvider interface.

Class diagram overview

And CFileProvider inherits from IDataProvider to implement GetDataFromImpl, and the same design can be used for CDataProcessing and CReportResult.

Here’s the new collaboration between the classes after refactoring:

Class diagram c2

Class factory

In the revised design, the concrete instances of IDataProvider, IDataProcessing, and IReportResult are created by the main method. A better approach is to assign this responsibility to a factory class, thereby isolating the logic required to instantiate the necessary family of objects.

Controller

The orchestration between all classes is implemented in the main method. It’s better to assign this responsibility to a Controller class so that it can be reused in other applications.

The controller needs to interact with three classes, so the question is: how do we bind these instances to the controller?

We can bind the instances using these two approaches:

    • Add a method named  BindInstances(IDataProviderPtr,IDataProcesingPtr,IReportResultPtr).
    • Define the controller as a template, and it will be instantiated like this:
CController<CFileProvider,CDataProcessor,CConsoleReport>

The difference between the two solutions is that for the first one, each data provider must inherit from IDataProvider, and for the second one, CFileProvider only needs the method GetData(Data&) even if it inherits from a class other than IDataProvider.

There are many discussions among C++ experts about whether to use OOP or templates. Here's a good article about this tension between the two approaches.

Here’s the new collaboration between the classes after the refactoring:

Class diagram c3

Benefits of the refactoring

After refactoring, the application becomes more flexible and can be used in different scenarios:

  • It can get data from a file, database, XML file, CSV file, …
  • It can process data using many different classes, not just one.
  • It can output results to the console, a file, …
Share this article