Skip to main content

Abstract Class vs Interface in C#


Alot of junior (maybe not only junior) developers has been asked in an interview "what is abstract class and how why do we need it if we have interfaces?". Let's try understand what is the abstract class, when it can be helpful and how it differs from an interface.
(Important note: in this article we will talk about classical abstract classes and interfaces. In some languages (like D or new java) some things can be different, so in the post I reference a C# language for examples.)


Differences

The main differences between abstract classes and interfaces are:
  • Abstract class can specify some implementation, which is common for all inherited classes
  • Interface provides no implementation, only the contract.
So you can see that abstract class defines a type hierarchy, and interface defines a contract. You can also see the difference as "A is B" vs "A implements B".

When would you use abstract class.

Let's imagine that we are writing a program that deals with animal. All animals have something common. But as we know in nature we have different animales like Cates, Dogs etc, but there's no just Animal in a real world. It means that we also will want to forbid creating instances of an Animal class in our program.
So we will end up with the next implementation of Animal:
public abstract Animal
{
    public abstract string Name {get;}

    public void Breathe()
    {
        Console.WriteLine(Name + " is breathing");
    }
}

You can ask, why won't we just use a regular class and won't create instances of Animal class in our code? The reason for this is that the usually we write code in teams and it will mean that not all members of the team will know that you are not allowed to create an instance of Animal and at some point they will try to do this and this can lead to an unpredictable behavior of your program. Marking the class as abstract helps us to avoid such situations. If someone will try to create an instance of the Animal class - compiler will warn him that the class is abstract and you are not allowed to create instances of this class. So the next what this teammate will do is probably ask the team why can't we use an Animal class in this way, or will try to read some documentation on this topic.
And as you see we've created a new hierarchy of classes in our program now: all animaler will inherit base abstract class Animal and implement required abstract methods plus have some common methods that are already implemented in Animal class.

When would you use an interface

Interfaces defines a contract that the class that inherit interface must implement. Let's get back to the animals examples.
When we create class Cat and inherit it from our base class Animal we say: Cat "is-a" Animal. Here we are creating a hierarchy.
But what if some animals can make noise but others not? We can't add it to a base class, because  this behavior is not common for all animals. Instead we want to say that Cat "has-a" Noise.
C# lets you derive from one class only. However you can implement as many interfaces as you like - this is because an interface is just a contract which your class promises to implement.And instead of adding all possible thing that Animals can do to a base class we can instead find something common to all of them and add some uncommon behavior to implementations via interfaces. Which will help us to work with some types of animals in one way and with other in a different way. For example: we can make animals that "has-a" Noise to make some noise, and don't ask animals that can't do noise for this.

Examples

Let's make an example to see the differences:
public abstract class Animal
{
    public abstract string Name {get;}

    public void Breathe()
    {
        Console.WriteLine(Name + " is breathing");
    }
}

public interface ISay
{
    void Say();
}

public class Cat : Animal, ISay
{
    public override string Name {get {return "Cat";}}

    public void Say()
    {
        Console.WriteLine("Meow");
    }
}

public class Fox : Animal
{
    public override string Name {get {return "Fox";}}
}

public class Program
{
    public static void Main(string[] args)
    {
        var cat = new Cat();
        var fox = new Fox();

        // var animal = new Animal(); // Cannot create an instance of the abstract class

        // All animals (cat and fox) can breathe.
        MakeBreathe(cat);
        MakeBreathe(fox);
        // Output:
        // Cat is breathing
        // Fox is breathing

        // But not all animals can say somthing (What does the fox say?)
        SaySomething(cat);
        SaySomething(fox);
        // Output
        // Meow

    }

    public static void MakeBreathe(Animal animal)
    {
        animal.Breathe();
    }

    public static void SaySomething<T>(T item) where T : class
    {
        if(item is ISay)
        {
            var speakable = item as ISay;
            speakable.Say();
        }
    }
}


Conclusion

 The key differences between abstract classes and interfaces are:
  • Abstract classes can have constants, members, method stubs (methods without a body) and defined methods, whereas interfaces can only have constants and methods stubs.
  • Methods and members of an abstract class can be defined with any visibility, whereas all methods of an interface must be defined as public (they are defined public by default).
  • When inheriting an abstract class, a concrete child class must define the abstract methods, whereas an abstract class can extend another abstract class and abstract methods from the parent class don't have to be defined.
  • Similarly, an interface extending another interface is not responsible for implementing methods from the parent interface. This is because interfaces cannot define any implementation.
  • A child class can only extend a single class (abstract or concrete), whereas an interface can extend or a class can implement multiple other interfaces.
  • A child class can define abstract methods with the same or less restrictive visibility, whereas a class implementing an interface must define the methods with the exact same visibility (public).
Also abstract classes can help us to protect base class from creating an instances of this class. And of course the difference in the logic of your code. Classes inherited from an abstract class says "I am A", when the classes inherited from interface says "I implements A".

Comments

Popular posts from this blog

How to Build TypeScript App and Deploy it on GitHub Pages

Quick Summary In this post, I will show you how to easily build and deploy a simple TicksToDate time web app like this: https://zubialevich.github.io/ticks-to-datetime .

Pros and cons of different ways of storing Enum values in the database

Lately, I was experimenting with Dapper for the first time. During these experiments, I've found one interesting and unexpected behavior of Dapper for me. I've created a regular model with string and int fields, nothing special. But then I needed to add an enum field in the model. Nothing special here, right? Long story short, after editing my model and saving it to the database what did I found out? By default Dapper stores enums as integer values in the database (MySql in my case, can be different for other databases)! What? It was a surprise for me! (I was using ServiceStack OrmLite for years and this ORM by default set's enums to strings in database) Before I've always stored enum values as a string in my databases! After this story, I decided to analyze all pros and cons I can imagine of these two different ways of storing enums. Let's see if I will be able to find the best option here.

Caching strategies

One of the easiest and most popular ways to increase system performance is to use caching. When we introduce caching, we automatically duplicate our data. It's very important to keep your cache and data source in sync (more or less, depends on the requirements of your system) whenever changes occur in the system. In this article, we will go through the most common cache synchronization strategies, their advantages, and disadvantages, and also popular use cases.

How to maintain Rest API backward compatibility?

All minor changes in Rest API should be backward compatible. A service that is exposing its interface to internal or/and external clients should always be backward compatible between major releases. A release of a new API version is a very rare thing. Usually, a release of a new API version means some global breaking changes with a solid refactoring or change of business logic, models, classes and requests. In most of the cases, changes are not so drastic and should still work for existing clients that haven't yet implemented a new contract. So how to ensure that a Rest API doesn't break backward compatibility?