Translate

C# Interfaces

Interface is a similar language construct such as class, but fundamentally different from a class. Some people claim that Interface is a way to implement multiple inheritance - this claim is also not true! If we were to implement inheritance via interfaces, then we would have re-used, but there is no code reuse while implementing Interfaces. Interface is an important language construct that every programmer should know so they can use it as a technique to create loosely coupled systems. Interfaces are a means to declare some capabilities or services so other classes may use them by implementing those interfaces.

In C#, Interfaces cannot have concrete implementations for the members. Hence, it can only declare properties and methods (not fields) that cannot have any access modifiers (public, private etc).

A very common usage of Interface is to support changing of certain behaviors of application without actually changing any code, but by -

                 - creating an interface that represents the generic behavior
                 - creating unique classes representing those changed behaviors
                 - extending the interface code (not changing it) by specifically implementing the interface methods via those unique classes
                 - putting the interface between the dependent class and the classes that represents the unique behaviors by defining the interface as a private property (belonging to the interface type) that gets instantiated within a constructor.
                 - implement members within the dependent class that uses this private variable to access the specific implementations of the interface method.
                 - in the main program, depending on the type of behavior that want to introduce within the dependent class, inject those specific implementations of unique behaviors/classes

I will explain this clearly by below examples -

1. Interface Class - IVisualiser
namespace InterfaceLearner
{
    public interface IVisualiser
    {
        void LogError(string message);
        void VisualizeData(string message);
    }
}

Note that the methods does not have access modifiers and does not have concrete implementation.

2. Define two specific / unique implementations of this interface for the sake of understanding

  2.1 HologramVisualizer
using System;
 
namespace InterfaceLearner
{
    public class HologramVisualizer : IVisualiser
    {
        public void LogError(string data)
        {
            Visualize(data, "ERROR");
        }
 
        public void VisualizeData(string data)
        {
            Visualize(data, "DATA");
        }
 
        public void Visualize(string data, string messageType)
        {
            if(messageType == "ERORR")
                Console.ForegroundColor = ConsoleColor.DarkRed;
            else if(messageType == "DATA")
                Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("\n" +messageType + " is visualized as a Hologram ::" + data);
        }
    }
}

  2.2 DataCubeVisualizer
using System;
 
namespace InterfaceLearner
{
    public class DataCubeVisualizer : IVisualiser
    {
        public void LogError(string data)
        {
            Visualize(data, "ERROR");
        }
 
        public void VisualizeData(string data)
        {
            Visualize(data, "DATA");
        }
 
        public void Visualize(string data, string messageType)
        {
            if (messageType == "ERORR")
                Console.ForegroundColor = ConsoleColor.DarkRed;
            else if (messageType == "DATA")
                Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("\n" + messageType + " is visualized as a Data Cube ::" + data);
        }
 
    }
}

Note that the concept is to visualize data in two forms - Hologram and normal DB data cube. See how we made complete implementation of the interface methods LogError and VisualizeData.

3. Class that needs to use two specific implementations of visualization - Vision
namespace InterfaceLearner
{
    public class Vision
    {
        private readonly IVisualiser _visualizer;
 
        public Vision(IVisualiser visualizer)
        {
            _visualizer = visualizer;
        }
 
        public void ViewVision()
        {
            _visualizer.VisualizeData("Visualizing Universe Data");            
        }
    }
}

See how we defined the private property _visualizer of type IVisualiser and we initialized it via class constructor so that each specific implementation of the interface is available within this class as and when it is injected into this class (we will see how we perform this injection in next step!).

4. Inject specific implementation of the interface via the main program
namespace InterfaceLearner
{
    class Program
    {
        static void Main(string[] args)
        {
            //inject DataCubeVisualizer
            var dataVisualizer = new Vision(new DataCubeVisualizer());
            dataVisualizer.ViewVision();
 
            //inject HologramVisualizer
            dataVisualizer = new Vision(new HologramVisualizer());
            dataVisualizer.ViewVision();
        }
    }
}

See how we inject specific implementation while instantiating the Vision object (this really means we are injecting the instantiation and storing of each specific class objects via Vision class constructor).

The beauty of using interfaces in this way is that the class Vision and specific implementations of behaviors are totally separated. Any changes to the specific implementation does not require to recompile the dependent class - thus leading to loose coupled behavior. Also we can add more specific implementations without recompiling the dependent class.

Another cool thing to note is how the concept of polymorphism is applied when the constructor (Vision) is overloaded with two different implementations of IVisualiser.