Previously, we looked at what interfaces are, and how some languages provide an Interface type, allowing you to formalize the exposed parts of your classes. Today we're going to consider the problems that an Interface type is designed to solve.
So, why would you want to use an Interface?
Interfaces give you polymorphism that's unshackled from any inheritance hierarchy.
Now, polymorphism is one of those $100 words people like to throw around to sound smart. In fact, if there were another concise word for it, I'd use that instead. But since there's not, let's look at a quick example. Polymorphism just means you can, for example, assign a subclass to a variable declared as a superclass, like so:
I declared a variable called rex and declared that it's a Plant (that's the superclass). But I assigned to it a new VenusFlyTrap (that's the subclass). VenusFlyTrap is a more specific type of Plant, so anything that a Plant can do, a VenusFlyTrap can do. That also means any method that takes a Plant argument can take a VenusFlyTrap. Since they're both part of the same inheritance tree, you can do this.
Now, most plants are content with just water and sunlight, but Rex here likes to eat bugs. And possibly Italian plumbers with overalls and ball caps. So he gets an extra method: consume(Bug bug).
Aardvarks aren't Venus Fly Traps
Aardvarks are fun. They look like a cross between a pig, a rat, and a kangaroo. Add to that the fact that they're one of the first words in the English dictionary, and you can see why they're so popular. But most importantly, they also eat bugs.
This poses a bit of a challenge. We've got two objects that both eat bugs, but they've got quite different inheritance hierarchies:
Now let's say your application requires you to collect a list of bug-eaters by passing them into a method. Well, VenusFlyTrap and Aardvark don't share much in common. You could just accept any LivingBeing, and hope that the calling code only sends in bug eaters. But you've got no guarantees. And besides, before you could call consume(Bug bug) on each object, you'd have to cast it to the appropriate subclass again. Ugh, what a mess.
Enter the Interface
This is where Interfaces come to the rescue. We know that both Aardvark and VenusFlyTrap expose a public method called consume(Bug bug). So we can declare an interface for that, and declare that both of those classes implement it.
Now we can pass either one of those into a method that accepts a BugEater.
Thanks to Interfaces, we were able to use polymorphism even though the Aardvark and the VenusFlyTrap have very different inheritance trees!
And why is that important?
- Simpler inheritance trees. In the example above, we didn't have to muddle up our inheritance tree with some nonsensical BugEater class between LivingBeing and the Plant and Animal classes. Nor did we have to add our consume(Bug bug) method to LivingBeing. We kept the methods on just the classes that use them, and kept an inheritance hierarchy that makes sense.
- Flexibility for the future. Methods that take an interface as an argument type are going to be more future-proof than methods that take a class as an argument type. The reason is that it doesn't tie your method to a particular inheritance tree. In the future, our addBugEater() method could take a Vacuum class (if it implements the BugEater interface), even though Vacuum wouldn't be a subclass of Plant or Animal, or even LivingBeing! Yeah, that's flexible!
- Even more polymorphism. Polymorphism that's free from inheritance is free from the restrictions that inheritance imposes. Most notably, languages like Java and C# do not allow you to inherit from more than one class. But since interfaces are not tied to inheritance, a class can implement any number of interfaces.
No comments:
Post a Comment