Click here to Skip to main content
15,881,172 members
Articles / Programming Languages / C#

Program to Interface, Not Implementation - Beginner's Tutorial for Understanding Interface, Abstract Class and Concrete Class

Rate me:
Please Sign up or sign in to vote.
4.68/5 (75 votes)
8 Jan 2014CPOL14 min read 178.8K   769   140   28
In this article, we will look into the details of Interfaces, Abstract class and Concrete class in C#. We will try to look at what each of them is and what should be used when to have a better design for the application.

Introduction

In this article, we will look into the details of Interfaces, Abstract class and Concrete class in C#. We will try to understand what each of them is and when should we use interfaces, abstract classes and concrete classes to have a better design for the application.

Background

Every once in a while, I see questions on "Interface, Abstract classes and Concrete classes" being asked on the CodeProject Q&A section and many other forums. Most of the time, it is the beginners who are asking the question. The reason such questions are being asked again and again is that the answers to the previous questions (a lot of very good answers) are getting lost in time and number of questions. And thus, I thought of creating this small article on some of the points related to these topics so that this could be used as a reference point for beginners to understand interfaces, abstract classes and concrete classes.

Using the Code

Let us start the discussion by looking at the "What" part. Once we understand this, we can dive into the "Why" and "When" in the later parts of the discussion.

What is a Concrete Class

A Concrete class or simply a class is a language construct that we can use to specify any meaningful entity. Meaningful entity can be thought of as any real world entities or business entities that need to be represented in our application. The other way to look at the class is as a blueprint. A blueprint to represent all the objects that have same attributes and behavior. Let us look at the Student Class for example.

C#
class Student
{
    DateTime dateOfBirth;
    public DateTime DateOfBirth
    {
        get { return dateOfBirth; }
        set { dateOfBirth = value; }
    }

    string firstName;
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }
        
    string lastName;
    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }
        
    string enrollmentNumber;
    public string EnrollmentNumber
    {
        get { return enrollmentNumber; }
        set { enrollmentNumber = value; }
    }

    public int GetAge(DateTime currentDate)
    {
        return currentDate.Year - dateOfBirth.Year;
    }
}

This Student class contain four attributes which are exposed as properties. It has a single behavior, i.e., Retrieving the age (in years) of the student on a given date. The class also contains four member variables which are used as backing fields for the four properties. In newer versions of C#, we can completely do away with the backing fields if there is no implementation logic in the properties. We can do this by implementing these properties as automatic properties.

C#
class Student
{
    public DateTime DateOfBirth { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EnrollmentNumber { get; set; }

    public int GetAge(DateTime currentDate)
    {
        return currentDate.Year - DateOfBirth.Year;
    }
}

A Note on Abstraction and Encapsulation

When we talk about designing classes, we need to think about two major things. First thing is "What will this class expose to the rest of the application/world?" i.e., the public methods and properties. The second thing is "How will we implement these exposed properties and methods?"

Abstraction mainly deals with "What this class will expose to the rest of the application"? Deciding the public interface of the class that will be used by the rest of the application is called the abstraction. Encapsulation on the other hand deals with the "How we will implement these exposed properties and methods"? This would include whatever code we write inside our properties and methods. This code is actually hidden from the users of this class. This is called encapsulation.

So in a way, what we are saying is that the exposed public properties and methods are the abstraction provided by the class. The internal implementation details of the properties and methods are hidden and are encapsulated within the class' internal implementation.

What is an Abstract Class

In the above code, we have seen that the Student class is able to implement all its behavior, i.e., all the methods. What if we have a class that does not know how to implement all the behavior. Suppose our student class also needs to provide a function GetFee(). But this class does not know how to implement this because the fee will depend on the type of student (assuming the students could be of multiple types, i.e., Regular and Online). So to represent this, let us change the student class in such a way that it has one method without any implementation.

C#
abstract class AStudent
{
    public DateTime DateOfBirth { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string EnrollmentNumber { get; set; }

    public int GetAge(DateTime currentDate)
    {
        return currentDate.Year - DateOfBirth.Year;
    }

    public abstract double GetFee();
}

Now since the method GetFee() doesn't have any implementation, we need to mark it as abstract. Since one of our functions is abstract, we need to mark the class as abstract. What this essentially says is that this class defines a public interface, i.e., an abstraction but it does not necessarily come with an implementation for all the functions. So if someone needs to inherit from this class, they need to provide the implementation for all those methods that are marked as abstract methods.

We cannot use abstract class directly because marking a class abstract indicates that this class is meant as a desired abstraction (contract so to say) and whoever wants to inherit from this will have to provide the implementation of the abstract methods.

C#
class RegularStudent : AStudent
{
    public override Double GetFee()
    {
        // fetch from some database and return
        return 10000f;
    }
}

So to use an abstract class, I created a Concrete class RegularStudent. This class exposes the same abstraction like AStudent since it is derived from Astudent but it also provides implementation for the abstract methods of AStudent.

Now let us take this concept of abstract class to an extreme and think about a class which has all the methods marked as abstract, i.e., a pure abstract class.

C#
abstract class AShape
{
    public abstract double GetArea();
    public abstract double GetPerimeter();
}

Now this is a pure abstract class. This means that, this class only provides the abstraction one should provide and it comes with no implementation. So the classes which will derive from this class will have to provide the implementation for all the methods of this class.

Before talking on pure abstract class, let us look at the interfaces first, then we will be in a better position to understand all the concepts in a better way.

What is an Interface

Interface is a language construct that lets us define a set of methods and properties in it. Any class that implements this interface must provide an implementation of this interface. This looks very similar to the concept of pure abstract class but before we dive deeper, let us try to create an interface for our shape problem.

C#
public interface IShape
{
    double GetArea();
    double GetPerimeter();
}

What this interface means is that it provides only the definition of the abstraction, i.e., a contract that all its implementers should provide. So to implement the interface, we need to provide the concrete class with the implementation of the interface.

C#
public class Square : IShape
{
    int _sideLength;

    public Square(int sideLength)
    {
        this._sideLength = sideLength;
    }

    public double GetArea()
    {
        return _sideLength * _sideLength;
    }

    public double GetPerimeter()
    {
        return 4 * _sideLength;
    }
}

So the Square class now implements the IShape and provides the implementation for the IShape interface. All the members of the interface are by default public, i.e., we don't need to provide access specifiers in the interface definitions.

Why Do We Need Interfaces and Abstract Classes

Now we know the "What" part of the classes, abstract classes and interfaces. It's time to understand the "why" part associated with the abstract classes and the interfaces. Let us try to understand it in a step by step manner:

The interface and abstract classes are a way of enforcing a contract on the concrete class. So we have to either inherit from an abstract class or implement interface if:

  1. we need multiple classes to behave in a polymorphic way.
  2. we need some kind of contract to enforce on the classes, put the contract in interfaces.

Now the major question is the choice between the interface and pure abstract class as both of them can be used to enforce a contract on the concrete class, which one should be used when. Let us look at some of the points that could help us answer this question.

  • An interface could be implemented by a class and also by a structure which is a value type, not a reference type. Whereas the abstract class can only be inherited by classes, i.e., reference type.
  • Polymorphism based on interfaces is more flexible then the one based on the base class. If we use polymorphism based on interfaces, the concrete type can either be of value or reference type.
  • Implementing interfaces is more flexible, i.e., a class can have only one immediate base class but it can implement more than one interface. Which would mean that our concrete class can adhere to more than one contract by implementing multiple interfaces. Which is not possible if our contract is specified as an abstract class rather than an interface.

When Should We Use Interfaces and Abstract Class

Interface: When we only need to enforce a contract. Whoever will implement this interface will provide an implementation of the methods.

Abstract class: When we need to implement a base type that knows how to implement some part of the abstraction, i.e., partial implementation and the other/remaining part of the abstraction cannot be implemented by this class. It leaves the responsibility of implementing remaining methods to the classes deriving from this class.

To Illustrate this, let us revisit the Shape problem. IShape interface is only a contract that says that whoever will implement it will have to provide an implementation for GetArea and GetPerimeter function.

C#
public interface IShape
{
    double GetArea();
    double GetPerimeter();
}

We can have a Circle class similar to the Square class above that can implement the IShape interface.

C#
public class Circle : IShape
{
    int _radius;

    public Circle(int radius)
    {
        this._radius = radius;
    }

    public double GetArea()
    {
        return Math.PI * _radius * _radius;
    }

    public double GetPerimeter()
    {
        return 2 * _radius * Math.PI;
    }
}

Now we a class like Quadrilateral is needed, we can make this class an abstract class because we can safely find the perimeter of this class by summing up all the sides but we cannot find the area unless the type of the Quadrilateral is known (mathematically it is possible, it is just a hypothetical scenario to design our abstract class).

C#
public abstract class AQuadrilateral : IShape
{
    public int Side1 { get; private set; }
    public int Side2 { get; private set; }
    public int Side3 { get; private set; }
    public int Side4 { get; private set; }

    public AQuadrilateral(int side1, int side2, int side3, int side4)
    {
        Side1 = side1;
        Side2 = side2;
        Side3 = side3;
        Side4 = side4;
    }

    public abstract double GetArea();

    public double GetPerimeter()
    {
        return Side1 + Side2 + Side3 + Side4;
    }
}

So this class still implements IShape because it promises to fulfill the contract and then it defines itself abstract to indicate that the contract has only been fulfilled partially. If we need to use this class, we need to implement the remaining part of the contract. Let us create a simple Rectangle class to see how it can be done.

C#
public class Rectangle : AQuadrilateral
{
    public int Width { get; private set; }
    public int Height { get; private set; }

    public Rectangle(int width, int height)
        : base(side1: width, side2: height, side3: width, side4: height)
    {
         Width = width;
         Height = height;         
    }

    public override double GetArea()
    {
        return Width * Height;
    }
}

We can see in the Rectangle class that it only needs to implement the remaining part of the contract specified by IShape and that is not implemented by the abstract class. The users of the Rectangle class should still work with the IShape handle. The handle will just be pointing to the concrete implementation of the Rectangle class.

Program to an Interface, Not an Implementation

Now that we have talked about interfaces, abstract classes and classes, we can summarize the discussion below:

  • Concrete class is the actual implementation.
  • Interface is the contract, it specifies the abstraction contract that its implementer should implement.
  • Abstract class is a trade off between the two, i.e., when we need to have the partial implementation of the contract, then we can use abstract class.

But why exactly should we care about the abstraction and contracts. Why can't we simply remove the interface and abstract classes and work with the concrete classes always. We are anyways free to create any number of versions for our concrete classes. Well, to find the answer to this question, we need to look at a best practice "Program to an interface, not an implementation".

What this best practice says is that the application should always use interfaces from other part of the application rather than the concrete implementations. There are several benefits of this approach like maintainability, extensibility and testablility. Anyone can write code that would make the application to work but the differentiating factor is whether or not this code is maintainable, extensible and testable. Let us try to look at these three concepts in details and understand how programming to an interface is always beneficial.

Let us think of a simple component which provides us with a Logger class.

C#
class LoggerBad
{
    public void LogMessage(string msg)
    {
        // Log this message here
    }

    public string[] GetLast10Messages()
    {
        return new string[]
        {
            "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" 
        };
    }
}

Now we need to use this class to log all the error messages. An Admin console also uses this class to get the recent 10 log messages. So the user of the class would do something like:

C#
class SomeClass
{
    public void SomeAction()
    {
        // lets use the bad logger here
        LoggerBad logger = new LoggerBad();
        string[] logs = logger.GetLast10Messages();

        foreach (string s in logs)
        {
            Console.WriteLine(s);
        }
    }
}

Now there are two problems here. One problem exists at the Logger component's end, i.e., it is only providing the concrete class and not the interface for the abstraction contract. Second problem lies at the usage end. The user code is actually using the concrete class handle (which can't be avoided since there is no interface) but also it is using an implementation i.e. string[] for the return type of the GetLast10Messages function.

Now let us see how we can use interfaces and create an application that is maintainable, extensible and testable.

Maintainability

Let's say due to some reasons the internal implementation of the Logger class has changed and the function GetLast10Messages is now returning a a List<string> instead of a string[]. The moment we take this changed code, we need to make a code change in all the places wherever we are calling GetLast10Messages method (it could be in thousands of places). So it is safe to say that the application is not maintainable. If we want our application to be maintainable, then we should have programmed it to an interface, i.e., the only use of the data we are receiving from the GetLast10Messages is with the foreach loop. Now instead of programming to implementation, i.e., string[] we should we used IEnumerable interface instead and it would have worked. Also, when the function start returning List instead of array, it will work too because we have programmed our application to an interface and not the implementation.

So, the code to use the LoggerBad class should look like:

C#
class SomeClass
{
    public void SomeAction()
    {
        // let's use the bad logger here
        LoggerBad logger = new LoggerBad();
        IEnumerable< string> logs = logger.GetLast10Messages();

        foreach (string s in logs)
        {
            Console.WriteLine(s);
        }
    }
}

Now the return value and internal implementation of the method will have no effect on the using application as long as the returning value implements IEnumerable interface. Thus making our application more maintainable.

Extensibility

Now we have solved only a part of the problem. If we were the designer of the component, then we should have specified an interface for the Logger class and implemented our component in this way.

C#
interface ILogger
   {
       void LogMessage(string msg);
       IEnumerable< string> GetLast10Messages();
   }

   class LoggerGood : ILogger
   {
       public void LogMessage(string msg)
       {
           // Log this message here
       }

       public IEnumerable< string> GetLast10Messages()
       {
           return new string[]
           {
               "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
           };
       }
   }

The benefit of this is that our component is fully extensible. If we need to add one more class LogToCloud then we simply need to implement the contract and our new class is ready for consumption from the application.

C#
class LogToClound : ILogger
{
    public void LogMessage(string msg)
    {
        // Log this message to the cloud using some web service
    }

    public IEnumerable< string> GetLast10Messages()
    {
        // call some web service and fetch the data
        return new string[]
        {
            "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" 
        };
    }
}

By including one interface, our component has become extensible. But let us again look at the usage.

C#
class SomeClass
{
    public void SomeAction()
    {
        // let's use the bad logger here
        LoggerBad logger = new LoggerBad();
        IEnumerable< string> logs = logger.GetLast10Messages();

        foreach (string s in logs)
        {
            Console.WriteLine(s);
        }
    }
}

There is still a major problem and the problem is that the user class is still programming to the implementation, i.e., the LoggerBad and not the interface. If I need to use selectively use LogToCloud, it would be possible for. We need to fix this. To fix this, let us program to interface and not to implementation.

C#
class SomeClassTwo
{
    public void SomeAction(ILogger _logger)
    {        
        ILogger logger = _logger;
        IEnumerable< string> logs = logger.GetLast10Messages();

        foreach (string s in logs)
        {
            Console.WriteLine(s);
        }
    }
}

Now this class is programmed to the interface and not to the implementation. If we want to use the LogToCloud, we just need to pass its concrete class instance and it will use LogToCloud. If we want to use our old LoggerGood, we can pass its concrete instance from the constructor and this class will use that.

C#
SomeClassTwo userclass2 = new SomeClassTwo();
// use our good old class
userclass2.SomeAction(new LoggerGood());
// Use the new cloud logger
userclass2.SomeAction(new LogToCloud());

Testability

Having unit testable applications also mean extensive use of interfaces rather than implementation. This is needed because we can then pass in the MOCK classes while unit testing our components.

The way it is achieved is:

  1. The using class will contain have the interfaces handles from the other parts of the application and use it to perform all the operations. It will not depend on the concrete class.
  2. The actual object of the concrete class will be passed to this class from the outside (Dependency Injection[^]) modules of the application.
  3. Our test project will pass in another concrete object which is a class mocking the actual functionality and implementing the same interface(contract) and thus making our class to use the mock object while being invoked from test project.

Creating Unit testable applications using interfaces itself is a big topic so I will not be discussing it here but to get more information on this please refer: Creating Unit Testable Applications in ASP.NET MVC - A Beginner's Tutorial[^]

Points of Interest

In this article, we have discussed about concrete class, abstract class and interfaces. We have seen when we should use abstract classes and when we should use interfaces. We have seen how interfaces are used to provide an abstraction contract. We have also seen how interfaces benefit us in creating maintainable, extensible and testable applications. This article has been written from a beginner's perspective. One of the motivating factors in writing this article is the frequent questions related to interfaces and abstract classes on CodeProject and other forums. I hope this article was somewhat informative.

History

  • 26th December, 2013: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India

I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems. IMO, C# is the best programming language and I love working with C# and other Microsoft Technologies.

  • Microsoft Certified Technology Specialist (MCTS): Web Applications Development with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Accessing Data with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Windows Communication Foundation Development with Microsoft .NET Framework 4

If you like my articles, please visit my website for more: www.rahulrajatsingh.com[^]

  • Microsoft MVP 2015

Comments and Discussions

 
PraiseExcellent - thanks Pin
SiAgar21-Aug-19 9:35
SiAgar21-Aug-19 9:35 
Well written and understandable.
QuestionFinally a useful article Pin
doktorfeelgood13-May-17 19:12
doktorfeelgood13-May-17 19:12 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey12-Jul-16 6:07
professionalManoj Kumar Choubey12-Jul-16 6:07 
GeneralExcellent Pin
Ishaq Sahibole17-May-15 20:42
professionalIshaq Sahibole17-May-15 20:42 
QuestionExcellent Pin
Member 1136671511-Jan-15 1:54
Member 1136671511-Jan-15 1:54 
Questiondetailed Pin
Member 112955558-Dec-14 11:40
Member 112955558-Dec-14 11:40 
QuestionQuality software Pin
Member 1127433329-Nov-14 21:01
Member 1127433329-Nov-14 21:01 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun11-Aug-14 19:54
Humayun Kabir Mamun11-Aug-14 19:54 
QuestionHere is another practical example: Pin
dietmar paul schoder2-Aug-14 10:26
professionaldietmar paul schoder2-Aug-14 10:26 
GeneralMy vote of 5 Pin
cotbot19-Jul-14 2:52
cotbot19-Jul-14 2:52 
QuestionVery nice article Sir Pin
Rahul VB20-Jan-14 8:51
professionalRahul VB20-Jan-14 8:51 
GeneralMy vote of 5 Pin
BarisOzdere15-Jan-14 22:33
BarisOzdere15-Jan-14 22:33 
GeneralMy vote of 5 Pin
slhCoding14-Jan-14 8:33
slhCoding14-Jan-14 8:33 
GeneralMy vote of 5 Pin
shr14-Jan-14 1:43
shr14-Jan-14 1:43 
QuestionMy vote of 4 - would you be able to re-read and correct the article. Pin
GuyThiebaut8-Jan-14 5:54
professionalGuyThiebaut8-Jan-14 5:54 
GeneralMy vote of 4 Pin
Nicholas Robert7-Jan-14 5:34
Nicholas Robert7-Jan-14 5:34 
GeneralMy vote of 5 Pin
ThatsAlok6-Jan-14 20:10
ThatsAlok6-Jan-14 20:10 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun5-Jan-14 22:21
Humayun Kabir Mamun5-Jan-14 22:21 
SuggestionShould we use an interface? Pin
Member 105023513-Jan-14 16:27
Member 105023513-Jan-14 16:27 
GeneralRe: Should we use an interface? Pin
johannesnestler9-Jan-14 0:06
johannesnestler9-Jan-14 0:06 
BugUsing decimals instead of floating point Pin
Steven M Hunt3-Jan-14 3:39
Steven M Hunt3-Jan-14 3:39 
QuestionA Question Pin
fixthebugg2-Jan-14 4:20
fixthebugg2-Jan-14 4:20 
AnswerRe: A Question Pin
Rahul Rajat Singh2-Jan-14 4:31
professionalRahul Rajat Singh2-Jan-14 4:31 
GeneralMy vote of 1 Pin
Member 88448601-Jan-14 23:21
Member 88448601-Jan-14 23:21 
SuggestionRe: My vote of 1 PinPopular
Reiss2-Jan-14 3:25
professionalReiss2-Jan-14 3:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.