Peter's blog

Musings (and images) of a slightly warped mind

Polymorphism, inheritance, and interfaces

Someone had trouble figuring out why .NET comes with all these interfaces for collections, so I thought, let’s write a concise blog post about that.

Okay, that was a joke. I don’t do concise.
And then I found out that I needed to explain interfaces, and polymorphism, and therefore inheritance, as well.

So, rather than staring at my screen like a bunny in the headlights of the oncoming train, I started to type.

Please note that I tend to include the odd cultural reference. Some are straightforward and widely known, and some are obtuse. Spotting them earns you brownie points.

The concepts of inheritance and interfaces are closely related. They both contribute to the concept of polymorphism in Java and c#, but they are distinctly different.
For the remainder of this article, we’ll stick to c#, but Java works conceptually the same way.

Inheritance is the ability of a class to inherit functionality from another class, using its properties and methods. This is particularly useful for classes that are related: a circle and a rectangle are both shapes, an apple and a lemon are both fruits, a tow truck and a motorcycle are both transportation vehicles.

Implementing an Interface is a way for a class to provide its own implementation of a defined behaviour. This allows you to create a relationship between unrelated classes, and to create a “contract” to which a class that implements the interface should adhere, telling the class what to do without telling it how it should do it.

An example of the application of an interface that comes to mind is a data repository. One could think of a repository interface (IRepository) as a contract on what data to get from a data storage container, but leave the how (how to query the container, how to convert the result) to the implementing class.

Inheritance

Rather than describe the concept abstractly, let’s pick an example and work from there.
Here is a base class called ShapeBase:

using System;

namespace PolyMorphismDemo
{
    public abstract class ShapeBase
    {
        public string Name { get; set; }
        public ShapeBase(string name)
        {
            Name = name;
            Console.WriteLine($"Instantiating Shapebase through {Name}");
        }

        public abstract void Draw();

        public abstract float GetSurface();

        public void Fill()
        {
            Console.WriteLine("Paint it, black");
        }

        public virtual void Erase()
        {
            Console.WriteLine($"...simsalabim, poof. Shape {Name} is now miraculously gone!");
        }        
    }
}

The abstract keyword tells us that this class cannot be instantiated on its own; it must be inherited. You can also inherit a class that can actually exist on its own, but this isn’t done too often, as it can lead to messy results.

We see that the base class can have a constructor, parameters, and code, which we will actually use.

We see that there are two methods marked as abstract, which means that a class inheriting ShapeBase must override those methods. An abstract method is the compiler’s way of telling the inheriting code “I’m sorry, Dave. I’m afraid I can’t do that.” You MUST have an implementation for this or it’s not going to compile. (and as we’ll see later, this kind of overlaps with the idea of an interface).

We also see a regular method called Fill, which tells the inheriting class “Relax, I got dis”, and a virtual method that tells the inheriting class “I can do this, but you may override me if you want”.

So, let’s go and work out something tangible using this base class:

using System;

namespace PolyMorphismDemo
{
    public class Circle : ShapeBase
    {
        public Circle(string name) : base(name) 
        {
            Console.WriteLine("Circle instantiated");
        }

        public override void Draw()
        {
            Console.WriteLine($"A circle named {Name} has been drawn.");
        }

        public override float GetSurface()
        {
            return 42f;
        }

        public override void Erase()
        {
            Console.WriteLine($"We will now make {Name} disappear.");
            base.Erase();
        }
    }
}

The class circle will implement the ShapeBase class by adding : ShapeBase to its definition.

When it’s instantiated, it will first instantiate the base class with the necessary parameters, and then execute the code in the body of its own constructor.

It provides implementations for the two abstract methods in the base class.

Note how it does not provide its own implementation for the Fill() method.

And to close things off, it will override the base class’s Delete method, executing its own code, and then call the Delete method in the base class! Is it not nifty?

Let’s see this in action.

using System;

namespace PolyMorphismDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            ShapeBase circle = new Circle("something round");
            Console.ReadKey();
            circle.Draw(); 
            Console.ReadKey();
            Console.WriteLine($"Surface area = {circle.GetSurface().ToString()}");
            Console.ReadKey();
            circle.Fill();
            Console.ReadKey();
            circle.Erase();
            Console.ReadKey();
        }
    }
}

Notable thing: we are calling the circle.Fill() method successfully, even through the Circle class has no Fill method of its own — it inherits this from the ShapeBase class.
Executing this will give the following output:

Instantiating Shapebase through something round
Circle instantiated
A circle named something round has been drawn.
Surface area = 42
Paint it, black
We will now make something round disappear.
...simsalabim, poof. Shape something round is now miraculously gone!

Note the order of the lines written to the console during instantiating time (where the constructor calls the base constructor before it does its own thing), and also the order of the lines when the Erase method is called.

A beginner’s guide to throwing shapes

OK, let’s spice things up a bit, and add another shape.

using System;

namespace PolyMorphismDemo
{
    public class Rectangle : ShapeBase
    {
        public Rectangle(string name) : base(name) 
        {
            Console.WriteLine("Rectangle instantiated");
        }

        public override void Draw()
        {
            Console.WriteLine($"A rectangle named {Name} has been drawn.");
        }

        public override float GetSurface()
        {
            return 100f;
        }
    }
}

As you can see, the Rectangle class provides us with the obligatory implementations of Draw and GetSurface, but it refrains from coming up with an override for Erase().

Let’s now add some code to our program to use this rectangle class, and see how this helps us to make the consuming code more versatile. Here’s a refactored version of our Program class:

using System;
using System.Collections.Generic;

namespace PolyMorphismDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            List<ShapeBase> shapes = new List<ShapeBase>();
            shapes.Add(new Circle("something round"));
            shapes.Add(new Rectangle("Spongebob"));
            
            Console.ReadKey();
            shapes[0].Draw(); 
            Console.ReadKey();
            Console.WriteLine($"Surface area = {shapes[0].GetSurface().ToString()}");
            Console.ReadKey();
            shapes[0].Fill();
            Console.ReadKey();
            
            shapes[1].Draw();
            Console.WriteLine($"Surface area = {shapes[1].GetSurface().ToString()}");
            Console.ReadKey();

            foreach (var s in shapes)
            {
                s.Erase();
            }
            Console.ReadKey();
        }
    }
}

Rather than working with separate Circle and Rectangle objects, we are now creating a list of shapes.
By addressing the list members separately, we can handle them separately, but we can also handle them with one swing of the arm by iterating through the members of the collection and then executing the Erase method on each of them. Note that, even though the Rectangle class doesn’t have its own Erase method, you can still call it, because it inherits the Erase method from its base class!
So, let’s execute this code — if only to see whether each derived class gets its own instance of the base class, or whether they share one (which would cause both the rectangle and the circle to be called Spongebob and have a surface area of 100):

Instantiating Shapebase through something round
Circle instantiated
Instantiating Shapebase through Spongebob
Rectangle instantiated
A circle named something round has been drawn.
Surface area = 42
Paint it, black
A rectangle named Spongebob has been drawn.
Surface area = 100
We will now make something round disappear.
...simsalabim, poof. Shape something round is now miraculously gone!
...simsalabim, poof. Shape Spongebob is now miraculously gone!

As we can see, even though we instantiate “something round” and then “Spongebob”, our objects keep getting the correct names and surface areas from their class (including base class) instantiation, so we’re good there.
Also, take a look at the output for deleting the Circle and rectangle removal. You can see that the Circle class provides its own implementation (which prints the “We will now make” line), but the rectangle doesn’t have its own implementation for Erase, so it relies entirely on the ShapeBase class to take care of that.

So yeah, that’s inheritance in a nutshell.
You can also create inheritance chains (where class C inherits class B, and class B inherits class A, providing C with A’s properties and methods). But what you cannot do is make C inherit A and B. That’s called Multiple Inheritance. And dot net (and Java) won’t allow you to do that, for the simple reason that it won’t make you shoot yourself in the foot as well as blow your entire leg off. It’s beyond the scope of this article to explain why, but if you’d want to know more, go ahead and google your heart out.

On top of that, I have yet to come across a real-world issue that would call for multiple inheritance that cannot be solve by the use of… tadaa… interfaces!

Why interfaces?

Interfaces provide a powerful way to make sure that classes you write to represent business entities adhere to a certain contract.
Implementing an interface in your class is related to, but not the same as, inheriting a base class, but they do share one idea: the ability to make two objects behave to their outside world in the same way.

An interface can be seen as a contract, specifying which properties and methods (including method signatures) should be exposed by all classes implementing that interface.
Contrary to a base class, it contains no shared code that the classes inheriting it can consume.

Let’s assume we’re going to persist our shapes in a database of sorts. We want to provide repositories for multiple database types, but leave it to each repository to decide how that’s done, rather than bore our business logic with it.

So, what we need is a single contract that tells us what functionality each repository should offer: the interface. it’s a good habit to prepend interface with a capital I, so our repository interface will be called IRepository:

namespace PolyMorphismDemo
{
    public interface IRepository
    {
        void Create(ShapeBase shape);
        ShapeBase Get(int id);
        void Update(ShapeBase shape);
        void Delete(ShapeBase shape);
    }
}

There is no usable code in here. All we see is a specification of methods with signatures that every class inheriting this interface will have to implement. How? We don’t care. Just do it.

Next, were going to create two repositories, one for use with an Obstacle database:

using System;

namespace PolyMorphismDemo
{
    internal class ObstacleRepository : IRepository
    {
        public void Create(ShapeBase shape)
        {
            Console.WriteLine($"Creating new record for {shape.Name} in Obstacle");
            // Obstacle implementation code goes here
        }

        public ShapeBase Get(int id)
        {
            switch (id)
            {
                case 0:
                    return new Circle("Circle");
                case 1:
                    return new Rectangle("Rectangle");
                default:
                    return null;
            }
        }

        public void Update(ShapeBase shape)
        {
            Console.WriteLine($"Updating record for {shape.Name} in Obstacle");
            // Obstacle implementation code goes here
        }
        public void Delete(ShapeBase shape)
        {
            Console.WriteLine($"Deleting record for {shape.Name} in Obstacle");
            // Obstacle implementation code goes here
        }
    }
}

and one for use with an AS404 database:

using System;

namespace PolyMorphismDemo
{
    public class AS404Repository : IRepository
    {
        public void Create(ShapeBase shape)
        {
            Console.WriteLine($"Creating new record for {shape.Name} in AS404");
            // AS404 implementation code goes here
        }

        public ShapeBase Get(int id)
        {
            switch (id)
            {
                case 0:
                    return new Circle("Circle");                    
                case 1:
                    return new Rectangle("Rectangle");
                default:
                    return null;
            }
        }

        public void Update(ShapeBase shape)
        {
            Console.WriteLine($"Updating record for {shape.Name} in AS404");
        }

        public void Delete(ShapeBase shape)
        {
            Console.WriteLine($"Deleting record for {shape.Name} in AS404"); 
        }
    }
}

As you can see, both repositories implement the IRepository interface (it might look as if it’s inheriting it, but at compile time, we know that it’s an interface), and both repositories implement all the methods that the interface prescribes, including the method signature.

What does this mean?

It means that the code you write doesn’t have to be bothered by whether you use an Obstacle or an AS404 database. It will just call the method needed to get it into the database.

And, just like objects instantiated from classes that inherit a base class, objects that are declared through an interface can be thrown into a typed collection. So you can do stuff like

foreach (IRepository rep in Repositories)
{
    ...
}

But wait, there’s more!

We see that our two repositories share code! Can’t we solve that with a repository base class?

Yes, we can. It’s going to get a bit trickier, but we can.

using System;

namespace PolyMorphismDemo
{
    public class RepositoryBase        
    {
        public void Log(string name, string operation, string dbType)
        {
            Console.WriteLine($"{operation} record for {name} in {dbType}");
        }

        public ShapeBase Get(int id)
        {
            switch (id)
            {
                case 0:
                    return new Circle("Circle");
                case 1:
                    return new Rectangle("Rectangle");
                default:
                    return null;
            }
        }
    }
}

This allows us to simplify the repositories a bit. In this solution, we can take out the Get method, and we can call the Log method to handle writing out what we are doing.

using System;

namespace PolyMorphismDemo
{
    internal class ObstacleRepository : RepositoryBase, IRepository
    {
        public void Create(ShapeBase shape)
        {
            Log("Create", shape.Name, "Obstacle");
            // Obstacle implementation code goes here
        }       

        public void Update(ShapeBase shape)
        {
            Log("Update", shape.Name, "Obstacle");
            // Obstacle implementation code goes here
        }
        public void Delete(ShapeBase shape)
        {
            Log("Delete", shape.Name, "Obstacle");
            // Obstacle implementation code goes here
        }
    }
}

This goes for the AS404 repository as well:

using System;

namespace PolyMorphismDemo
{
    public class AS404Repository : RepositoryBase, IRepository
    {
        public void Create(ShapeBase shape)
        {
            Log("Create", shape.Name, "AS404");
            // AS404 Create implementation code goes here
        }

        public void Update(ShapeBase shape)
        {
            Log("Update", shape.Name, "AS404");
            // AS404 update implementation code goes here
        }

        public void Delete(ShapeBase shape)
        {
            
            Console.WriteLine($"Deleting record for {shape.Name} in AS404");
            // AS404 delete implementation code goes here
        }
    }
}

Now, all we have to do is add a Save method to our Rectangle and Circle classes. But wait… since both Rectangles and Circles are Shapes, and inherit the ShapeBase class, we can add that Save code in ShapeBase:

using System;

namespace PolyMorphismDemo
{
    public abstract class ShapeBase
    {
        public string Name { get; set; }
        private IRepository rep = null;

        public ShapeBase(string name) : this(name, null) { }

        public ShapeBase(string name, IRepository repository)
        {
            rep = repository;
            Name = name;
            Console.WriteLine($"Instantiating Shapebase through {Name}");
        }

        public abstract void Draw();

        public abstract float GetSurface();

        public void Save()
        {
            if (rep == null) throw new Exception("Repository not set");
            rep.Create(this);
        }

        public void Fill()
        {
            Console.WriteLine("Paint it, black");
        }

        public virtual void Erase()
        {
            Console.WriteLine($"...simsalabim, poof. Shape {Name} is now miraculously gone!");
        }        
    }
}

Good, we’re all set in the business logic too. Let’s redecorate our “front end” a bit:

namespace PolyMorphismDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            IRepository repository = new ObstacleRepository();

            List<ShapeBase> shapes = new List<ShapeBase>();
            shapes.Add(new Circle("something round", repository));
            shapes.Add(new Rectangle("Spongebob", repository));
            
            Console.ReadKey();
            shapes[0].Draw(); 
            Console.ReadKey();
            Console.WriteLine($"Surface area = {shapes[0].GetSurface().ToString()}");
            Console.ReadKey();
            shapes[0].Fill();
            Console.ReadKey();
            shapes[0].Save();
            Console.ReadKey();
            shapes[1].Draw();
            Console.WriteLine($"Surface area = {shapes[1].GetSurface().ToString()}");
            Console.ReadKey();
            shapes[1].Save();

            foreach (var s in shapes)
            {
                s.Erase();
            }
            Console.ReadKey();
        }
    }
}

What you see here is that, in our “user interface” (program.cs), we determine which implementation of the repository we’re going to use.

IRepository repository = new ObstacleRepository();

Here, it’s hardcoded, but you could base that on a setting in a config file.

We then pass that setting into the shape object. The shape object does nothing with it except passing it into its base class, and the base class then gets a ready-made repository to use in its Save method.
Need to support an additional data container (SQL, MySql, what have you)? Write a repository that implements the IRepository interface, and provides the relevant code for that data container in the implemented methods, use the RepositoryBase to use any shared code, and you’re set. All business logic remains unchanged, you just need to add a new repository type to the configuration options and tell program.cs about it.

This design pattern is called Dependency Injection, it is incredibly useful, and to use it, you will need to make use of interfaces.

Importance of interfaces as contracts in cross-platform applications

We just touched upon one suitable application for interfaces, but there are many more.

Think of writing multiplatform code. Your legacy application is targeting .NET Franework 4.8, but you want to gradually migrate your solution to .NET Core. New development is done in, say, .NET Core 6, but you want to be able to use that adjacent to your .NET Framework code.

It is vitally important that you can consistently count on your existing business entities to expose the same functionality in both realms. So, you’d define your interfaces in an environment that is accessible for both .NET Framework and .NET Core — which just so happens to be .NET Standard 2.0. If you make sure that both your existing .NET Framework business logic as well as your newly written .NET Core business logic implement the same interface, you can pretty much migrate with little pain while avoiding a Big Bang migration scenario.

Testing and dependency injection

If you want to do automated testing of your business logic, but you want to leave certain components (for instance, data access) out of the equation, you can use a concept called mocking.

This involves writing a mock component (a mocked repository, for instance) that implements the same interface as the real deal, but yields predictable results without having to depend on the availability of external dependencies (for instance databases, OCR engines, external services).
You can then use dependency injection to inject the mocked classes into your business logic.

Questions? Comments?

By now, you should have a good basic understanding of using inheritance and designing and implementing interfaces. If you still have questions, or additional comments, constructive criticism, or you spotted a glaring error, feel free to put them forward in the comments. I do not pretend to know everything there is to know about .NET, but I know how to find stuff out!

Next Post

Leave a Reply

© 2024 Peter's blog

Theme by Anders Norén