Composition and Interfaces in OOP World

In the object-oriented programming world, the concept of inheritance has been criticized for a long time.

There are quite a lot of arguments:

  • A derived class inherits all the parent’s data and behavior that is not always necessary. When modifying a parent class, some data and behavior that are not supposed to be in the derived class get into it;
  • Virtual methods are less productive. If a language allows declaring a non-virtual method, what would you do when you need to override one in an inherited method? You can mark the method with the new keyword, however, in this case, polymorphism will not work. The consequent use of this object may cause unexpected behavior, depending on the type the object is going to be converted to.
  • The vast majority of languages do not allow multiple inheritance.
  • There are tasks where inheritance is helpless. Consider the case, when you need to create a container (array, list, or set) and implement the same behavior for elements with different types. In addition, you need to provide static typing. In this case, generics will come to help.

An alternative to inheritance in OOP is using interfaces and composition in object-oriented programming. Interfaces have long been used as an alternative to multiple inheritance, even if inheritance is actively used in a class hierarchy.

Inheritance allows avoiding code duplication. There can be objects with the same or similar set of properties and methods, but having partially or completely different behavior or implementation mechanisms of this behavior. In this case, to eliminate code duplication you will have to use other mechanisms.

How can we avoid code duplication when using interfaces and composition in object-oriented programming? This is exactly what we are going to talk in this article!

Let’s declare an interface and two or more classes that implement this interface. Each class that implements the interface provides the individual implementation for MethodA and equal implementation for MethodB.

The easiest way to eliminate code duplication is to create a helper class with static methods, which take the required variables as arguments. Different classes would call these static methods inside MethodB and pass required values.

It is not necessary to implement the helper class as static. You can make it an instance strategy-class.  In this case, it is better to pass the input to the constructor of the strategy class, rather to its methods.

I am going to describe how to use this approach on a particular example.

Assume we need to implement a set of classes that allows authenticating a user in the system. Authentication methods will return instances of entities that we are going to call AuthCredentials. They will contain the authorization and authentication information about the user. These entities must have methods like bool IsValid () that allow verifying the validity of each AuthCredentials instance.

Step1

The main idea of the proposed approach is to create a number of atomic interfaces that would act as different ways of representing the AuthCredentials entity, as well as the interfaces representing the atomic interfaces composition. Additionally, we need to create extension methods for each interface. As a result, we will have a single code base that allows us to work with any interface implementation. It is important to note that the extension methods can only work with the properties and methods defined in interfaces, but not with internal data.

Additionally, we need to create extension methods for each interface. As a result, we will have a single code base that allows us to work with any interface implementation. It is important to note that the extension methods can only work with the properties and methods defined in interfaces, but not with internal data.

Let’s create a Windows Console Application solution in Visual Studio:

HelloExtensions is a console application where we are going to call the main code that is separated by class libraries.

HelloExtensions.Auth is the main library containing the interfaces that allow demonstrating the considered solution;

HelloExtensions.ProjectA.Auth is a library containing the code that implements interfaces defined in HelloExtensions.Auth;

HelloExtensions.ProjectB.Auth is a library containing the code for alternative implementation of the interfaces defined in HelloExtensions.Auth.

Step 2

Let’s define the following interfaces in the HelloExtensions.Auth project. Note that proposed interfaces serve for demo purposes only.

ICredentialUser – a user can authenticate with their login or another identifier without the possibility of anonymous authentication and creating a user session. In the case of successful authentication, we get a UserID, otherwise null.

The IcredentialToken – a user can authenticate anonymously. In the case of success, we get the identifier (session token), otherwise null.

The IcredentialInfo – traditional authentication with creating a user session. The interface is a composition of the ICredentialUser and IcredentialToken interfaces.

The IencryptionKey – in the case of successful authentication, an encryption key is returned. A user can use this key to encrypt data before sending it to the system.

The IcredentialInfoEx interface is a composition of the ICredentialInfo and IEncryptionKey interfaces.

Step 2.1.

Now, it is time to determine utility classes and other data types in the HelloExtensions.Auth project.

Note that all utility classes’ logic is for demo purposes only. It is implemented as stubs.

The TokenValidator class provides the logic that validates the token identifier. For example, it checks for

  • valid values
  • internal consistency
  • existence of an identifier in the system database
The IdentifierValidator class provides the logic that validates the identifier. For example, it checks for

  • valid values
  • existence of an identifier in the system database
The KeySize enumeration is the list of allowable encryption key length (in bits), with the definition of internal values as the key length in bytes.
The KeySizes class lists acceptable length of encryption keys.
The KeyValidator class provides a logic to check an encryption key validity.

Step 2.2.

In the HelloExtensions.Auth project, we will declare the CredentialsExtensions class, which provides extension methods for the above-mentioned interfaces that declare different AuthCredentials structures depending on the way a user authenticates to the system.

As you can see, a required IsValid method will be selected depending on the interface a variable implements. The fullest method will be selected at the compile time. For example, the IsValid(this ICredentialInfo info) is selected for the variable that implements the ICredentialInfo interface.

Still, there are some details we should keep in mind:

  • The fullest method is going to be selected in case a variable is casted to its original type or the fullest interface. What is going to happen if we cast a variable that implements the IcredentialInfo interface to the IcredentialUser interface in the code? It will lead to incorrect check of the AuthCredentials structure, because the IsValid method will call IsValid(this ICredentialUser user).
  • Is it correct to have the IsValid(this ICredentialInfoEx info)and IsValidEx(this ICredentialInfoEx info) methods to be simultaneously used? It turns out that incorrect and incomplete check is possible for the ICredentialInfoEx interface.

Thus, there is no interface polymorphism in the current implementation of extension methods.

To move on, we need to redesign the interfaces for different AuthCredentials structures and the CredentialsExtensions class in the following manner.

Let’s create the empty IAuthCredentials interface from which other interfaces will be inherited (a parent interface for all AuthCredentials structures).

We do not need to override composition interfaces, as they inherit IAuthCredentials automatically. In addition, it is not necessary to override atomic interfaces, which do not require creating individual implementations. IEncryptionKey in our case.

In the CredentialsExtensions class, we will keep only one public extension method, which works with IAuthCredentials:

Now, all check occur at runtime. Therefore, when implementing the IsValid method (this IAuthCredentials credentials) it is important to check if the interface implementation is run in a correct order (from the fullest interface to the emptiest one) to provide the correct check of the AuthCredentials structure.

Step 3.

Now we can add logic that implements interfaces AuthCredentials from HelloExtensions.Auth to HelloExtensions.ProjectA.Auth and HelloExtensions.ProjectB.Auth projects.

The common way is as follows:

  1. Determine necessary interfaces that inherit interfaces from HelloExtensions.Authand add project-specific declarations.
  2. Create stubs for these interfaces;
  3. Create an additional infrastructure with stubs that provides authentication API (the infrastructure has the following sequence: interface, implementation, and factory).

Project A

Interfaces:

Interface implementation:

Note: Names of entities may differ from the ones specified in the interface; in this case, it is necessary to implement the interface explicitly by wrapping the reference to the corresponding entity.

Infrastructure API:

Project B

Interfaces:

Interface implementation

Infrastructure API

Step 3.1.

Let’s add calls of providers’ authorization methods from Project A and В to a console application. Each method will return variables of an interface that inherits IAuthCredentials. To check each variable, we will call the IsValid method. Done.

Thus, it is possible to implement a separate set of methods without using copy/paste for different entities having both similar and different functionality.

This method can be applied both for developing an application and for refactoring.

Additionally, I would like to point out why this task cannot be performed using classical inheritance: entities in the A and В projects implement functionality specified for each project – in the first case, entities can be serialized into/from XML, in the latter case – into/from JSON.

It is an illustrative difference. Still, we may face it in real projects, where entities may differ even more.

In other words, if there is a set of entities that similar in a functional part (some statements use UserId and SessionToken, whereas other statements have EncryptionKey), then extensions methods will help create a unified API that will work with these entities.

This article suggests ways of working with these extension methods.

Resume

I would like to add that the proposed approach is more acceptable for cases when there are entities (classes) similar to their functionality in different assemblies of the project, and the top-level assemblies (client code) work with these entities in a similar way.

How can we unify the work with these entities without refactoring? It is a good idea to declare a set of interfaces with exactly the same properties and methods as those defined in classes that are going to implement these interfaces.

Most probably, you will have to explicitly declare some interface elements in classes.

Then, we implement a class with extension methods for new interfaces. Then, we use one extension method instead of the “copy/paste”, everywhere we have a reference to the classes.

Thus, the proposed approach is suitable for the legacy code when it is necessary to quickly fix and implement a set of classes with similar declarations and functionality. We do not take into account the way such a set of classes can appear in the project.

When developing a project API and class hierarchy from scratch, other approaches should be applied.

How it is possible to build a code without copy/paste if two or more classes have the same method which differs in logic only is a separate topic. Perhaps, it is a topic for another article.

Andrey Sergeev

Andrey Sergeev

Andrey Sergeev is experienced DDD, C# and .NET developer, and is interested in Java and Ruby stacks.
For last ten years, Andrey developed the multi-tier traceability system and business analysis systems.
In present, Andrey is developing the famous mobile fitness application.
Andrey Sergeev

Andrey Sergeev

Andrey Sergeev is experienced DDD, C# and .NET developer, and is interested in Java and Ruby stacks.
For last ten years, Andrey developed the multi-tier traceability system and business analysis systems.
In present, Andrey is developing the famous mobile fitness application.