The Interface vs Abstract class

What is the difference between an abstract class and an interface, and when would you use one over the other

This is almost a guaranteed interview hurdle that any engineer or developer would likely have to answer. The question has a few different answers depending on some context, and I want to dive a little deeper into these two abstraction tools, and how each can be used. First let’s define both.

Abstract Class

The term abstract class is defined in the glossary of the book ”Design Patterns: Elements of Reusable Object-Oriented Software” as:

A class who’s main purpose is to define a common interface for its subclasses. An abstract cannot be instantiated.

These classes are usually accompanied by some kind of modifier that explicitly declares them as abstract, and they can include class properties, concrete implemented methods, and abstract methods.
Note: all code syntax will be a pseudo C# language, syntax may vary in different languages but the concepts remain the same.

abstract class MyAbstractClass {
	// abstract classes can have class properties
	int classProperty1
	
	// Concrete/non-abstract methods
	public void DoSomething() {
		// implementation details
		Console.WriteLine(abstract class);
	};
	
	// abstract methods
	abstract public void SomethingElse();
	// no implementation can be defined for abstract methods
};

One of the traits of an abstract class is that it will defer some or all of its implementation to its subclasses. Due to the deferred implementation, an abstract class by itself will not have all of the details it needs to be instantiated. So we cannot use things like var instanceOfMyAbstractClass = new MyAbstractClass();, this would result in a some sort of compilation error because there would be no implementation for the SomethingElse() function.

Instead what we want to do is allow subclasses to define how to use these abstract methods.

// class that extends the abstract class
public class SubClass1 : MyAbstractClass {
	public override void SomethingElse(){
		// concrete implementation of the method
		Console.WriteLine(SubClass1);
	};
};

// second class that extends the abstract class
public class SubClass2 : MyAbstractClass {
	public override void SomethingElse(){
		Console.WriteLine(SubClass2);
	};
};

With the above example we created 2 subclasses that extend the same abstract class, if we call those class methods then the language would resolve each call to the correct implementation of the function at runtime.

// we are assuming default constructors exist in this language

// define a variable the type of our abstract class
MyAbstractClass test;

	// instantiated a subclass of the abstract class
test = new SubClass1();

test.SomethingElse();
// Outputs: “Subclass1”

test.DoSomething(); // concrete method from the abstract class
// Outputs: “abstract class”

test = new SubClass2();

test.SomethingElse();
// Outputs: “Subclass2”

test.DoSomething(); // concrete method details do not change
// Outputs: “abstract class”

An abstract class give both traditional Object Oriented Programming(OOP) concrete inheritance, and abstract features that can be implemented by subclasses. At runtime your language will now which implementation of the method to resolve. This “runtime resolution” is known as Polymorphism, but we will cover that later.

Interface

Interface is also defined by the same book as before as:

The set of all signatures defined by an object’s operations. The interface describes the set of requests to which an object can respond

Interfaces are a grouping of all the method signatures that an object that uses the interface can utilize. Interfaces are not classes, thus they cannot be instantiated.

Interfaces, like abstract classes, are accompanied by an keywords that identify them as an interface. By convention most languages also name interfaces with a leading i, casing can depend on language standards.

public interface IMyInterface {
	public void MyMethod1();
	public void AnotherMethod(int x);
};

Unlike abstract classes, things that implement an interface are required to provide concrete implementations for every method in the interface.

// This class would fail to compile because
// we do not provide an implementation for AnotherMethod()
public class SubClass1 : IMyInterface {
    // override keyword is not required for interfaces
    public void MyMethod1(){
        Console.WriteLine(SubClass1 Method1);
    };
}

// this satisfies the requirements to implement an interface
public class SubClass2 : IMyInterface {
    public void MyMethod1(){
        Console.WriteLine(SubClass2 Method1);
    };
    
    public void AnotherMethod(){
     Console.WriteLine(SubClass2 AnotherMethod);
  };
}
    

Since our first subclass would not compile, we can see how SubClass2 can be used.

 var test = new SubClass2();
 
 test.MyMethod1();
 // Outputs: “SubClass2 Method1”
 
 test.AnotherMethod();
 // Outputs: “SubClass2 AnotherMethod”

Interfaces are used to represent common object interactions that can be shared by many classes. We can reference interfaces when we call objects in segments of code and it doesn’t matter what the underlying object is, as long as it implements our common interface then we can use that object.

Are they different?

Yes and no, both an interface and abstract class serve the purpose of abstracting away concrete implementation details, but the capabilities of each are different. These capabilities should help determine when to use one over the other. Both implementations of abstraction represent a contract with any object that will extend/implement the abstraction. The contract simply means that"in order to pretend to be this interface/abstract class, then I have to provide implementations for all the things it needs". We can use both to an interface and an abstract class to be used by downstream code in place as something “abstract” without any concrete details that we might not know until our code is running.

Why is abstraction important?

Let’s use a real world example: Pretend you built an app that uses a type of database A. You discover that database A has some limitations that impact your app’s users and you need to switch to database B to address some performance issues.

However, you discover that database ‘B’ has different queries and syntax for communications between the database and your app. Now you must re-write some of your app’s core logic affecting how data flows through your app. These changes could cause a serious refactor in your code, as well as producing potential bugs and issues that users will find later. Not to mention the effort you will have to spend by refactoring your code.

Enter abstraction. If the app had some kind of abstract reference to the database like this 👇

// Interface defined
public Interface IDatabase{
	public string GetData(int id);
	public void UpdateData(int id);
	public string CreateData(string data);
	public void DeleteData(int id);
}

// Or alternatively 

// Abstract Class defined
public abstract class AbsDatabase{
	string databaseName;
	string databaseUser;
	string databaseConnectionURL;
	
	abstract public string GetData(int id);
	abstract public void UpdateData(int id);
	abstract public string CreateData(string data);
	abstract public void DeleteData(int id); 
}

then we no longer have to touch any of the upstream code that consumes the abstract reference. All that would be required is that we build the concrete implementations that are specific to database B and presto, we have a way to interact with database B and our app’s logic remains unchanged.

When to use one over the other?

The Abstract Class gives us the ability to specify concrete details, things that will remain the same for all subclasses that extend the abstract class. With an abstract class we can have subclasses that override abstract methods to accommodate their individual implementation of that method. So if you know that your base class will have things that remain constant for any subclasses, but you will also have methods that are subclass specific then you will need to utilize an abstract class.

The Interface gives the ability to only specify method signatures with no concrete implementation details, we can have classes that implement an interface and that class is responsible for providing the concrete details that our interface does not have. Once we do this we can use instances of our interface in place of concrete objects.

Closing notes

IDK I had a lot of coffee writing this, hope its legible