Tutorials… no, I don’t use tutorials usually. Especially not for these concepts as I learned them many years.
I have noticed people struggle with interfaces, I myself had a bit of a battle with them myself when I was learning OOP concepts.
So ok… I’ll try to explain them in the way I understood them… which means I’m going to have to talk about something NOT to do with interfaces as you may see them in C#.
OK… so OOP (object oriented paradigm) operates on some fundamental concepts:
Encapsulation, Abstraction, Polymorphism
and Inheritance, though the OOP community at large has mostly realized inheritance isn’t exactly perfect, and shouldn’t get the fundamental label so much anymore. Which honestly is where interfaces as you see them in C# sort of spawns from.
OK, so what are these things.
Encapsulation
This is the concept that objects should have 2 distinct parts. An interior and an exterior. These are called its ‘implementation’ and its ‘interface’ respectively.
I like to use the VCR as an example (I don’t know how old you are, but hopefully you remember what the VCR/VHS is). A VCR has a chassity to it with some inputs and outputs. There is the slot in which you slide the VHS tape, buttons for play/pause/rewind/fastforward/stop/eject, and some outputs like video and audio out.
This is its interface. It’s the controlled structure through which you’re allowed to interact with the VCR.
If you crack it open though, you’ll see all the gizmos and parts that make up the VCR. What actually occurs when you insert a tape, what actually occurs when you push play, so on and so forth.
This is its implementation. It’s the guts of the object that you as a user don’t give 2 shits about.
All you need to know is the intent of those buttons and input and outputs. We don’t care that play moves a lever that opens the flap of the tape and draws it around the read head of the VCR and then starts a motor spinning the tape. WE JUST CARE THAT IT MAKES VIDEO COME OUT!
Furthermore, this interface means that Sony/JVC/etc can all make their own VCR and change around the guts to make it cheaper, or higher fidelity, or whatever they decide… yet the interface looks the same to you the end user. Same play button, same slot to stick a VHS tape in, etc.
This is encapsulation.
We see this in classes with the access modifier. You create ‘public’ and ‘private’ members (fields/methods/properties/etc), the public members are those outward facing buttons that can be tinkered with. You could see them like this:
public class VCR
{
public AVSignal Output { get; };
public void Load(VHS tape);
public void Play();
public void Pause();
public void ToggleFFW();
public void ToggleRW();
public void Stop();
public void Eject();
public void Record(AVSignal input);
}
I don’t show the code inside those functions/methods, because we don’t care about them. This is the interface of the class (not interface as you see it in C#, we’ll get to that… you screw a screw with a screwdriver).
Abstraction
So encapsulation has separated implementation from interface of a class.
Abstraction is the idea of separating concrete class from the idea of how you interact with the class… its interface.
So back to the VCR… for years disc like systems attempted to reliquish the VCR from its position as home media player. There was laser disc, and VCD, and so on… and of course the DVD which we all know and love from the late 90’s on until now where even BluRay has a hard time subverting it.
Now… there’s something you may notice about all of these things though. THEY HAVE THE SAME INTERFACE.
They all have a slot in which you load the media, they have play/pause/stop methods. Sure the DVD also comes with a ‘chapter forward’ and ‘chapter backward’ button… but they still maintaint he fastforward and rewind functionality of the VCR.
They have COMMON interfaces. And with purpose… it makes it easy for a consumer buying this new technology to understand how to use it. You get a DVD player, and it works similar to a VCR.
So you might see as a class:
public class DVDPlayer
{
public AVSignal Output { get; };
public void Load(DVD disc);
public void Play();
public void Pause();
public void ToggleFFW();
public void ToggleRW();
public void Stop();
public void Eject();
public void ChapterForward();
public void ChapterBackward();
}
Now you might notice, we no longer have a Record functionality, because DVD’s can’t be recorded to. But we’ve gained the ChapterFW and Back functions.
Furthermore the Load function accepts a different input.
But other than that the interface is VERY similar.
What if we could describe these things as related?
So now we get into the contention of inheritance… I’m going to show you this example with inheritance/abstract class, but now a days this is considered poor design. But keep in mind OOP has been around a long time and has grown… but yeah, this is why I removed inheritance from the fundamental concepts of OOP.
public class VideoMedia
{
}
public class DVD : VideoMedia
{
}
public class VHS : VideoMedia
{
}
public class VideoMediaPlayer
{
public AVSignal Output { get; };
public void Load(VideoMedia media);
public abstract void Play();
public abstract void Pause();
public abstract void ToggleFFW();
public abstract void ToggleRW();
public abstract void Stop();
public abstract void Eject();
}
public class VCR : VideoMediaPlayer
{
public AVSignal Output { get; };
public overloads void Load(VHS tape) : base.Load(tape);
public void Play();
public void Pause();
public void ToggleFFW();
public void ToggleRW();
public void Stop();
public void Eject();
public void Record(AVSignal input);
}
public class DVDPlayer : VideoMediaPlayer
{
public AVSignal Output { get; };
public overloads void Load(DVD disc) : base.Load(disc);
public overrides void Play();
public overrides void Pause();
public overrides void ToggleFFW();
public overrides void ToggleRW();
public overrides void Stop();
public overrides void Eject();
public void ChapterForward();
public void ChapterBackward();
}
So now, we could be handed a VideoMediaPlayer, told it’s media is already loaded, and call Play/Pause/Stop/etc to our hearts content… never needing to concern ourselves with what sort of videomedia it plays. Which leads to…
Polymorphism
Polymorphism is the concept of how you can treat 2 objects similarly because their interfaces are similar.
It’s the direct result of abstraction.
Inheritance
So the big evil monster that is inheritance.
Inheritance comes with weird issues that may not be recognizable in the examples I posted above. But it all comes down to that ‘implementation’ part.
See ‘VideoMediaPlayer’ can contain implementation in it for whatever purpose. Maybe it sets up the AVSignal stuff, because that’s most the same from media player to media player… but wait, what if you do that, but then a NEW media video output standard comes about (HDMI?), this means we must retrofit it into the existing VideoMediaPlayer class… and what if that standard doesn’t mesh well with outdated mediums like VHS and LaserDisc, which are inherently analogue signals.
Another issue comes up is that what if you define a class that several classes inherit from. But you create one that is both a VideoMediaPlayer but ALSO an AudioMediaPlayer (like a CD-player, or mp3-player).
public class AudioMediaPlayer
{
AudioSignal Output {get;}
public void Play();
public void Pause();
public void Stop();
public void Skip();
}
public class CDPlayer : AudioMediaPlayer
{
}
public class MP3Player : AudioMediaPlayer
{
}
public AdvancedDVDPlayer : VideoMediaPlayer, CDPlayer, MP3Player
{
//so on so forth
}
If I call Play, what am I playing?
If MP3Player already has code implementing Play, and so does CD player… how do I tell the compiler which function I actually want called?
Everything is getting weird now…
This is beginning to border on what is called the ‘diamond problem’ in inheritance:
Multiple inheritance - Wikipedia
Multiple inheritance is proving to create quagmires… so what do we do? Ban multiple inheritance (which C# does), but how do we get to be a DVD/CD/MP3 player?
In steps a NEW concept that was introduced to OOP to replace Inheritance…
Composition
Composition is the idea that instead of inheriting from a type, you instead consume that part and make it accessible through the interface.
public class CDPlayer : AudioMediaPlayer
{
}
public class MP3Player : AudioMediaPlayer
{
}
public class DVDPlayer : VideoMediaPlayer
{
}
public AdvancedDVDPlayer
{
public CDPlayer CD {get;}
public MP3Player MP3 {get;}
public DVDPlayer DVD {get;}
}
BUT, now we have a layer between us and each device. We can’t just hand around the AdvancedDVDPlayer… what if we want to flip functionality based solely on what type we want it to be at that time instead of having to reach inside of it like this (breaking encapsulation/abstraction, fundamental principals of OOP).
…
An explicitly defined Interface!
Interfaces - we screw a screw with a screwdriver
OK, so up to this point I’ve said the word ‘interface’ multiple times, but kept bringing up that it’s not the interface you see in C#.
That interface is the keyword used for explicitly defining interfaces.
At first you might think its weird they have the same name… but that’s why I say “we screw a screw with a screwdriver”. We often use the same word for different things and you determine which we’re talking about by context.
“we define an interface to abstract the interface of our class”
So now we can say this:
public interface IAudioMediaPlayer
{
AudioSignal Output {get;}
void Play();
void Pause();
void Stop();
void Skip();
}
public interface ICDPlayer : IAudioMediaPlayer
{
}
public interface IMP3Player : IAudioMediaPlayer
{
}
public interface IVideoMediaPlayer
{
AVSignal Output { get; };
void Load(VideoMedia media);
abstract void Play();
void Pause();
void ToggleFFW();
void ToggleRW();
void Stop();
void Eject();
}
public interface IDVDPlayer : IVideoMediaPlayer
{
}
Note, we don’t define public/private… it’s an interface. Interfaces are implicitly public, it’s the public facing members of an object.
public class CDPlayer : ICDPlayer
{
public AudioSignal Output {get;}
public void Play();
public void Pause();
public void Stop();
public void Skip();
}
public class MP3Player : IMP3Player
{
public AudioSignal Output {get;}
public void Play();
public void Pause();
public void Stop();
public void Skip();
}
public class DVDPlayer : IDVDPlayer
{
public AVSignal Output { get; };
public overloads void Load(DVD disc) : base.Load(disc);
public overrides void Play();
public overrides void Pause();
public overrides void ToggleFFW();
public overrides void ToggleRW();
public overrides void Stop();
public overrides void Eject();
public void ChapterForward();
public void ChapterBackward();
}
public AdvancedDVDPlayer : ICDPlayer, IMP3Player, IDVDPlayer
{
public ICDPlayer CD {get;}
public IMP3Player MP3 {get;}
public IDVDPlayer DVD {get;}
public void ICDPlayer.Play() { CD.Play(); }
public void IMP3Player.Play() { MP3.Play(); }
//so on so forth
}
And now our AdvancedDVDPlayer can be passed around and treated as any of its 3 types, without any of the clashing that comes with multiple-inheritance.
You MIGHT ask, dafuq I now would have to write all these forwarding methods as well!?
Yeah… that’s a sucky parts about how interfaces work in C# (and Java as well, where C# stole it from back in the late 90’s). Interfaces at the time of inception were created before composition was fully embraced and was like a “best of both worlds” compromise… programming is an evolving trade… so it’s a little broken when it comes to multiple composition. But for single composition, or for multiple inheritance it’s great.
Think of the collection types like ‘IEnumerblae’ and ‘IList’. You can quickly turn any class into a collection by merely implementing these interfaces.
Newer languages learned from this flaw in the design and come up with solutions.
Such as ‘duck typing’, in languages like python due to how its interpretted and ran, you can do a thing called ‘duck-typing’. The idea is that if “it walks like a duck, quacks like a duck, then it’s a duck”. Basically if an object has a member with some name, then it can be treated like a type that has that member name. There are flaws here again with types that have intersecting method names… but it’s a way of dealing with it.
Another… which I think is BEAUTIFUL and I hope to god that the language gets adopted far more…
Golang
Golang does away with inheritance all together and instead embraces composition completely.
You don’t even really have classes really… instead we remove the class and return back to ‘struct’ (a data structure…). But you get interfaces as well. The interfaces are implemented implicitly. And then you can composite types by just making other structs a member of your defined type:
type shape struct {
x,y float64
}
type rectangle struct {
shape shape
width,height float64
}
rectangle can now be treated like a shape.
It implicitly knows because you’ve included ‘shape’ as a member, that you can directly access it. You don’t have to say ‘myrect.shape.x’ you just say ‘myrect.x’.
BUT if rectangle happened to composite a type that ALSO has an ‘x’ property. You can say ‘myrect.shape.x’ to explitly clarify which x you’re talking about.
Interfaces in this regard are also implied. If you define an interface
type geometry interface {
area() float64
}
Then you defined a function for rectangle called area
func (r rectangle) are() float64 {
return r.width * r.height;
}
Now rectangle is implicitly a geometry. And ANY function that takes in a geometry, can accept rectangles polymorphicly.
Unfortunately golang isn’t getting as popular as I wish it would. Primarily because the direction languages are leaning these days are ‘functional’ paradigm by and large. This is the result of software scale shrinking for the most part. We’re moving towards a lot of small applications in the web that can be easily maintained in procedural and/or functional paradigms.
And larger software in the open-source community is embracing the ‘linux/gnu’ philosophy of many small programs that are good at doing one thing. Which honestly I think of as moving object oriented concept out of the language, and into the OS… which I find… maybe not the best idea. I mean think about it, these many programs are encapsulated in themselves, and often API standards are designed to make them interchangeable if you prefer another company/groups implementation. Thusly making them abstracted (the standard api) and polymorhpic (interchangeable).
But as a result, OOP is dying out, except for in the large enterprise field where Java/C# dominate. And when efficiency is wanted (like games) C++ is king because it’s baremetal, and no one wants to convert to golang there because it’d mean rewriting HUGE libraries with decades of history to them.
…
Anyways… I got on a bit of a tangent with golang there.
But yeah… THAT’S the purpose of ‘interface’.
And yes, it’s VERY powerful.