I saw often people use this code like
interface IAnimal
{
void die();
}
class Cat : IAnimal
{
void die() { ... }
void meow() { ... }
}
IAnimal anAnimal = new Cat();
Cat aCat= new Cat();
C# knows for sure anAnimal.die()
works, because die()
is defined in IAnimal
. But it won’t let you do anAnimal.meow()
even though it’s a Cat
, whereas aCat
can invoke both methods.
Why and when we should write this kind of code:
IAnimal anAnimal = new Cat();
What is the advantage?
Because although the compiler could deduce that the meow()
call would succeed in this case, it can’t do so in every conceivable program, and it’s much better to have a simple, easy-to-remember policy of “no type inference” than “I’m going to try some type inference for ya, but I’m warnin’ you, I’m not gonna find all the demonstrably legal calls you might expect, so no guarantees.”
Therefore, if you’re going to call meow()
anyway, there is no point whatsoever in declaring something as Animal
. Just go ahead and admit that it’s a Cat
. Interfaces are a useful construct, but they can earn their pay only if you’re willing to let them do their abstraction thing, and forget about the exact species.
In general, it rarely makes any sense to declare something as an abstract supertype when you then immediately assign it to a constructor call, i.e. a concrete type. But imagine you had a DogFactory
, a CatFactory
and a BirdFactory
, all of which implement a common PetFactory
interface. The PetFactory
would contain a method Animal getPet()
, and each concrete factory would do something like return new Cat()
. The concrete type is needed to actually construct an object; the interface is needed so that different providers can all satisfy the same interface, which simplifies weaving together the application at a higher level.
IAnimal anAnimal = new Cat();
You’re right. This is doesn’t really have a use case, since it has no direct advantage. Since you’re hardcoding the object’s type (Cat
), there is nothing gained from downcasting the type of the variable that references the object.
However, your argument does not hold true when Cat
is not hardcoded:
IAnimal anAnimal = myAnimalFinder.FindByName("Fido");
You don’t know what animal you’re going to get back.
Assuming your software is sold to vets, the result (based on the same name) may differ from vet to vet. Vet A has treated a dog named Fido, vet B has treated a cat named Fido.
This is the real use case, one that actually makes sense in context. The simplified version you were initially referring to is most commonly found in examples, not live code.
But it won’t let you do anAnimal.meow() even though it’s a Cat
Looking at the real use case, it becomes clear why you can’t call meow()
. You have no way to guarantee that you’re actually going to get a Cat
back, so there’s no way to know if the returned animal can meow or not.
4
If you wider the context a little bit, you can find a pretty good use-case for this, like dependency injection. Imagine having a class that depends on another one and calls its method. You want to implement it as a loose coupling code with a minimum dependency between those two classes, so you can replace the dependent class with some other, without breaking the functionality of the class you are calling that second class from.
If you use e.g. constructor injection and assign class passed in a constructor’s parameter into a private variable based on your interface type, you can call its methods anywhere in that class and be sure that any class passed in implements that method, as they have to, since they must implement your interface. Should you be calling some methods from the class variable instead of the interface variable, you could not ensure that every single class you can pass in implements that method, as interface says what must some class implement, but does not forbid it from implementing anything else on top of that.
A typical example is DI via constructor injection and interface e.g. in the HomeController in some ASP.NET Core MVC app:
public class HomeController : Controller
{
//Private variable of the interface type into which you assign a class injected in the constructor
private readonly IGreetingService _greetingService;
//A constructor accepting any class that implements your interface
public HomeController(IGreetingService greetingService)
{
_greetingService = greetingService;
}
public string Hello(string id)
{
//You call the Greet method via the interface as you can be SURE
//that it has this method implemented from the class you have passed in
return _greetingService.Greet(id);
}
Interfaces generally define behaviours and classes that implement those interfaces are guaranteed to exhibit those behaviours.
interface IDriveable
{
drive();
}
class BMW : IDriveable
{
drive();
setAirConditioning( int temperature );
}
class ModelT : IDriveable
{
drive();
}
Both cars can be driven.
The shiny, new BMW can also do the air-con thing, but that’s not a feature of being driveable.
Let’s say today your are using a “cat” object, but tomorrow you might decide that a “dog” object is better.
Code that only uses what’s declared in the interface doesn’t need changing, so you want to avoid using cat-specific code that doesn’t work with a “dog” object. And the easiest way to avoid cat-specific code is to assign the cat object to a variable of the “animal” interface type.
The disadvantage: Neither the cat’s “meow” method nor the dog’s “bark” method can be used. The advantage: The same.