Sujith
Posted on July 11, 2019
Prelude
Just so you know I'm not an Interface wizard who knows every nook and cranny that exists with an interface. I am just gonna explain how one can effectively use an interface and make their day/code less cumbersome and a lot cleaner. Also, a few elements of the examples will have some unity keywords in it, But that will not bog you from learning the fundamental concept that I'm trying to explain. Without further ado, let's jump into the topic.
What is an Interface?
An interface is a base class with a bunch of method declarations with no definitions, This interface class cannot work on its own as all its methods are just declarations. It will expect some other class to inherit this interface and upon inheriting the interface will make it mandatory to the inherited class to define all the method declarations inside it. In essence, an interface tells a class about the methods it needs to have in order for the class to perform certain functions but the interface will not tell what the function does, It will expect the writer of that class to know it.
I hope this much is clear, let's continue with an example ,
A human will perform a function of eating without which a human will die. If the human were a class we interface the class human with an Interface IEat
(People always precede Interface with an "I" so let us follow the same rule, It's not necessary but I'd advise everybody to follow the same).
This
IEat
will only add a method to the class Human called Eat and this method will have a parameter called consummate amount. Remember this method or the parameter from the interface does not tell us how to eat or how much to eat. They will only tell these things to exist in class and later the writer of the class should specify how to eat the food and how much/what food to eat Human.
This is how the code for the above-mentioned functionality will look like
IEat.cs Interface
public interface IEat {
void Eat(int consumateamount);
}
Human.cs
public class Human : IEat
{
public void Eat(int consumateamount)
{
PutItInMouth(GetFood(consumateamount));
}
private Food GetFood(int consumateamount)
{
Food food = new Food();
food.apple = consumateamount;
food.meat = consumateamount;
return food;
}
public void PutItInMouth(Food food)
{
//Human Eats
}
}
public class Food
{
public int apple;
public int meat;
}
So now let's see how we could use this Eat(int consumateamount) for a human test subject.
Let's consider that the human brain has a class called BrainSchedule, Inside the class we have each time cycle of a day as a method and inside every method we call the Eat(int consumateamount) method for an instance of a human subject. Here the amount of food consumed is determined by the time cycle method.
public class BrainSchedule : MonoBehaviour
{
private Human myHuman;
private void Morning()
{
myHuman.Eat(2);
}
private void Noon()
{
myHuman.Eat(4);
}
private void Evening()
{
myHuman.Eat(1);
}
private void Night()
{
myHuman.Eat(2);
}
}
As you could see the BrainSchedule class assumes that the Human class has an eat method inside it and calls that method at different cycles of the day to keep the human well nourished.
We've just learned what an Interface is, But this is not the best application of it.
Thus we have a make-believe scenario here where Interface could be used at it's best.
Let's say we are maintaining a zoo, and this zoo has a variety of animals. Each animal will have its own base class. We as the zookeepers our main job here is to feed them. Even though each animal has its own unique food preference we can segregate them based on their food habits into three different categories.Herbivores, Carnivores and Omnivores
Writing the feeding system
We will have a common class called Feeding System which allocates the amount of food and feeds it to the respective animals based on the category. The important point to be noted here is each category will have different subcategories. For example, Herbivores eat foods ranging from hay to fruits. Some animals like Koala will eat the only eucalyptus. According to our design, the feeding system will only segregate foods based on the main categories, If Koala does not want to eat berries and only eat eucalyptus then it is up to the Koala to ignore the berries and consume eucalyptus, and will not be spoon-fed by the feeding system. (This in the real life would be a very irresponsible feeding system but for the sake of learning the Interface we are gonna bear with it).
Let's define what sort of foods these categories gonna have
[System.Serializable] //Just so I could view this over the inspector
public class HerbivoreFood
{
public int hay;
public int strawberry;
public int eucalyptus;
}
[System.Serializable]
public class CarnivoreFood
{
public int salmon;
public int steak;
public int lamb;
}
[System.Serializable]
public class OmnivoreFood
{
public CarnivoreFood carnivoreFood;
public HerbivoreFood herbivoreFood;
}
Let's create three interfaces based on the three different categories, Each of this category based interfaces will impose one method that takes in the food type of its category type. For example, Iherbivore interface should have an EatGreens method that takes HerbivoreFood instance as an argument. The code sample is provided below
public interface IHerbivore
{
void EatGreens(HerbivoreFood herbivoreFood);
}
public interface ICarnivore
{
void EatMeat(CarnivoreFood carnivoreFood);
}
public interface IOmnivore
{
void EatGreensAndMeat(OmnivoreFood omnivoreFood);
}
And let's create a base class for every animal in the zoo and we will make each animal inherit an interface that suits with their food habits. For example, If an animal likes berries, It will have access to those berries only if the feeding system recognizes that animal is an herbivore and sends it herbivore based food. For the feeding system to recognize a given animal is herbivore or not we need to inherit the base class of an animal from the IHerbivore interface. (If the animal only wants to eat meat then it should inherit from the ICarnivore interface or else if it wants both then it should inherit from the IOmnivore interface.) The code for the base classes are given below.
public class Koala : MonoBehaviour, IHerbivore
{
private int stomach;
public void EatGreens(HerbivoreFood herbivoreFood)
{
EatEucalyptus(herbivoreFood.eucalyptus);
}
private void EatEucalyptus(int eucalyptus)
{
stomach += eucalyptus;
}
}
As mentioned earlier due to Koala's inheritance from the IHerbivore class, It will be sent with all of the Herbivore based food but if you look properly at the class you'll realize that after receiving all of the herbivore based food it eats only what it wants to eat which is the eucalyptus and leaves the rest.
public class Panther : MonoBehaviour, ICarnivore
{
private int stomach;
public void EatMeat(CarnivoreFood carnivoreFood)
{
EatSteak(carnivoreFood.steak);
}
private void EatSteak(int steak)
{
stomach += steak;
}
}
Same way the panther eats only the steak even if it has access to lamb inside the CarnivoreFood Class.
public class Bear : MonoBehaviour,IOmnivore
{
private int stomach;
public void EatGreensAndMeat(OmnivoreFood omnivoreFood)
{
EatBerries(omnivoreFood.herbivoreFood.strawberry);
EatFish(omnivoreFood.carnivoreFood.salmon);
}
private void EatBerries(int berries)
{
stomach += berries;
}
private void EatFish(int salmon)
{
stomach += salmon;
}
}
And bear as usual being an omnivore eats both berries and salmon.
Now let's write the feeding system
The Feeding system should first have access to all the animals in the zoo, So we have all the animals inside the allZooAnimals array which is the type of a GameObject. (It is a Unity specific type don't pay much attention to it, just consider it as a common reference type). So far we have three animals scripts made so we will have three animals in the array and each animal GameObject will have one of the three animal scripts attached to them. As we have all of our animals now we create objects for each food type and initialize it inside the Start() method. After that, we call the FeedAllAnimals() method, which will recognize the type of each animal script attached to each GameObject in the array and call the appropriate method and pass the appropriate food type based on its category.
public class FeedingSystem : MonoBehaviour
{
public GameObject[] allZooAnimals;
[SerializeField]private HerbivoreFood herbivoreFood;
[SerializeField]private CarnivoreFood carnivoreFood;
[SerializeField]private OmnivoreFood omnivoreFood;
private void Start()
{
///Get food ready
herbivoreFood = new HerbivoreFood();
herbivoreFood.hay = 50;
herbivoreFood.eucalyptus = 50;
herbivoreFood.strawberry = 100;
carnivoreFood = new CarnivoreFood();
carnivoreFood.steak = 60;
carnivoreFood.salmon = 100;
carnivoreFood.lamb = 100;
omnivoreFood = new OmnivoreFood();
omnivoreFood.herbivoreFood = herbivoreFood;
omnivoreFood.carnivoreFood = carnivoreFood;
FeedAllAnimals();
}
private void FeedAllAnimals()
{
for (int i = 0; i < allZooAnimals.Length; i++)
{
IHerbivore herbivore = allZooAnimals[i].GetComponent<IHerbivore>();
if (herbivore != null)
{
herbivore.EatGreens(herbivoreFood);
}
ICarnivore carnivore = allZooAnimals[i].GetComponent<ICarnivore>();
if (carnivore != null)
{
carnivore.EatMeat(carnivoreFood);
}
IOmnivore omnivore = allZooAnimals[i].GetComponent<IOmnivore>();
if (omnivore != null)
{
omnivore.EatGreensAndMeat(omnivoreFood);
}
}
}
}
The GetComponent inside the FeedAllAnimals() method will get the instance of the animal as the type of the interface, We do this and not the base class of the animals because, the base class might have a hell lot functionalities which the feeding class does not need to know about, By accessing the instance as an interface we know that these methods inside the interface will compulsorily be written inside the class that inherits it, so we can pass the data with confidence also we use this to type check what sort of food that particular instance of the animal eats without having to know anything about that animal apart from its food habit just through the interface.
If you're buying a new animal to the zoo, Fret not! Just add an interface based on their diet to the animal and they will be ready to be fed, For example, see this Tiger and Zebra.
public class Tiger : MonoBehaviour, ICarnivore
{
private int stomach;
public void EatMeat(CarnivoreFood carnivoreFood)
{
EatLamb(carnivoreFood.lamb);
}
private void EatLamb(int lamb)
{
stomach += lamb;
}
}
public class Zebra : MonoBehaviour, IHerbivore
{
private int stomach;
public void EatGreens(HerbivoreFood herbivoreFood)
{
EatHay(herbivoreFood.hay);
EatBerries(herbivoreFood.strawberry);
}
private void EatHay(int hay)
{
stomach += hay;
}
private void EatBerries(int berries)
{
stomach += berries;
}
}
So this marks the end of this tutorial or whatever you call this is, According to me this is the best use of an interface I've seen so far. Maybe there is more to the interface than meets the eye, if you know that is the case lets discuss it in the comments section below.
P.S This is like the first blog that I've ever written in my entire life so if you see places where I could improve both in writing and programming please do let me know, Thanks XOXO
Posted on July 11, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.