Introduction
In this lesson, you will learn how to create and implement your own interfaces with a concrete example and you will get an idea of why interfaces are useful especially when our classes don’t have a relationship with each other and therefore they don’t share a base class.
Example
Imagine that we are working on a video game where the player can destroy stuff, But everything we destroy will have different behavior depending on what is destroyed.
for example, destroying a Chair will play some destruction sounds and will spawn destroyed chair parts, but destroying a car will play an explosion sound, create fire and destroy other objects nearby.
For our game we have the following classes:
- Chair that inherits from class Furniture
- Car that inherits from class vehicle
//base class for furnitures
class Furniture {
//color of the furniture
public string Color { get; set; }
//material of the furniture
public string Material { get; set; }
//default constructor
public Furniture() {
Color = "White";
Material = "Wood";
}
//simple constructor
public Furniture(string color,string material) {
Color = color;
Material = material;
}
}
//base class for vehicles
class Vehicle {
//speed of the vehicle
public float Speed { get; set; }
//color of the vehicle
public string Color { get; set; }
//default constructor
public Vehicle() {
Speed = 120f;
Color = "White";
}
//simple constructor
public Vehicle(float speed,string color) {
Speed = speed;
Color = color;
}
}
//subclass chair that extends Furniture
class Chair: Furniture {
//simple constructor
public Chair(string color,string material) {
this.Color = color;
this.Material = material;
}
}
//subclass Car that extends Vehicle
class Car: Vehicle{
//simple constructor
public Car(float speed,string color){
this.Speed = speed;
this.Color = color;
}
}
Now what is the best approach to add this destruction feature?
If we think about it, we can’t add a class to make our classes inherit because C# is a single inheritance language and not multi-inheritance. We already inherited from one class.
Plus, there is no concrete relationship between a car and a chair to share one base class anyways.
If we implement normal methods called Destroy in each class, it will be hard to maintain. What if we decide to change the destruction system entirely or add visual effects to our destructible objects? It will be a pain actually to go through each class and modify it.
What we need is a way to enforce our destructible behavior on every class that uses it just like a contract :)
The best approach, in this case, is to use an Interface we will call it IDestroyable, and any class that implements this interface will be forced to follow our destruction requirements and customize it at the same time depending on the class itself.
This interface will define two things
- A property called DestructionSound which will store the audio file for the destruction sound
- A method called Destroy which each class will implement differently
//defining an interface called IDestroyable
interface IDestroyable {
//property to store the audio file of the destruction sound
string DestructionSound { get; set; }
//method to destroy an object
void Destroy();
}
Now let’s make our classes implement this interface and implement the interface’s content
//implementing the interface IDestroyable
class Chair: Furniture, IDestroyable {
//implementing the interface's property
public string DestructionSound { get; set; }
public Chair(string color,string material) {
this.Color = color;
this.Material = material;
//initializing the interface's property with a value in the constructor
DestructionSound = "ChairDestructionSound.mp3";
}
//implementing the interface's method
public void Destroy() {
//when a chair gets destroyed we should play the destruction sound
//and spawn the destroyed chair parts
Console.WriteLine($"The {Color} chair was destroyed");
Console.WriteLine("Playing destruction sound {0}",DestructionSound);
Console.WriteLine("Spawning chair parts");
}
}
//implementing the interface IDestroyable
class Car: Vehicle, IDestroyable{
//implementing the interface's property
public string DestructionSound { get; set; }
//creating a new property to store the destroyable objects nearby
//when a car gets destroyed it should also destroy the nearby object
//this list is of type IDestroyable which means it can store any object
//that implements this interface and we can only access the properties and
//methods in this interface
public List<IDestroyable> DestoryablesNearby;
public Car(float speed,string color){
this.Speed = speed;
this.Color = color;
//initialize the interface's property with a value in the constructor
DestructionSound = "CarExplosionSound.mp3";
//initialize the list of destroyable objects
DestoryablesNearby = new List<IDestroyable>();
}
//implementing the interface's method
public void Destroy() {
//when a car gets destroyed we should play the destruction sound
//and create fire effect
Console.WriteLine("Playing destruction sound {0}", DestructionSound);
Console.WriteLine("Create fire");
//go through each destoryable object nearby and call it's destroy method
foreach(IDestroyable destroyable in DestoryablesNearby) {
destroyable.Destroy();
}
}
This way, if we decide to add a property or a method to our interface, for example: Delete destroyed objects, after some time, all classes that implement this interface will be forced to follow the new requirements since now they are part of the interface’s contract :).
Now let’s go to our main method and create few objects of each class and use try the new destruction feature
using System;
using System.Collections.Generic;
namespace OOPInterfaces {
class Program {
static void Main(string[] args) {
//creating two objects of type chair
Chair officeChair = new Chair("Brown", "Plastic");
Chair gamingChair = new Chair("Red", "Wood");
//creating a new object of type car
Car damagedCar = new Car(80f, "Blue");
//add the two chairs to the IDestroyable list of the car
//so that when we destroy the car the destroyable objects
//that are near the car will get destroyed as well
damagedCar.DestoryablesNearby.Add(officeChair);
damagedCar.DestoryablesNearby.Add(gamingChair);
//pause
Console.ReadKey();
}
}
}
Important note: Interfaces are used for Communication between 2 similar/nonsimilar classes, which do not care about the type of the class implementing the Interface, just like how our car communicated with the chair to call the Destroy method, all because they implemented the same interface.
While interfaces might seems hard to understand at first glance, they provide us with great perks, for example:
- Code readability: An interface constitutes a declaration about intentions. It defines the capability of your class, what your class is capable of doing. If you implement ISortable, you’re clearly stating that objects of your class can be sorted.
- Code semantics: By providing interfaces and implementing them, you’re actively separating concepts. An interface defines a behavioral model, a definition of what an object can do. Separating those concepts keeps the semantics of your code more clear.
- Code maintainability: Interfaces help reduce coupling and allow you to easily interchange implementations for the same concept without the underlying code being affected.
- Design Patterns: It’s the bigger picture of using contracts, abstraction, and interfaces pivotal for OOP, human understanding, and complex system architectures.
- Multiple inheritance: complex Using interfaces can be our gateway to use multiple inheritances in C#
Outro
Suppose you write a library and want it to be modifiable by users. You write the interface and its class implementation. Other developers who will use your library can still write their own implementation class, which may use different technology/algorithms that achieve the same result. This is also why we meet so many interfaces in libraries we use but rarely feel the need to write our own interfaces because we don’t write libraries.
On a final note, the important thing is to understand how interfaces are implemented at this point of the course. As we go through different technologies like WPF and ASP.Net, we will start using interfaces more, which will help us better understand them in the future.
Complete Code
using System;
using System.Collections.Generic;
namespace OOPInterfaces {
class Program {
static void Main(string[] args) {
//creating two objects of type chair
Chair officeChair = new Chair("Brown", "Plastic");
Chair gamingChair = new Chair("Red", "Wood");
//creating a new object of type car
Car damagedCar = new Car(80f, "Blue");
//add the two chairs to the IDestroyable list of the car
//so that when we destroy the car the destroyable objects
//that are near the car will get destroyed as well
damagedCar.DestoryablesNearby.Add(officeChair);
damagedCar.DestoryablesNearby.Add(gamingChair);
//destory the car
damagedCar.Destroy();
//pause
Console.ReadKey();
}
}
//base class for furnitures
class Furniture {
//color of the furniture
public string Color { get; set; }
//material of the furniture
public string Material { get; set; }
//default constructor
public Furniture() {
Color = "White";
Material = "Wood";
}
//simple constructor
public Furniture(string color,string material) {
Color = color;
Material = material;
}
}
//base class for vehicles
class Vehicle {
//speed of the vehicle
public float Speed { get; set; }
//color of the vehicle
public string Color { get; set; }
//default constructor
public Vehicle() {
Speed = 120f;
Color = "White";
}
//simple constructor
public Vehicle(float speed,string color) {
Speed = speed;
Color = color;
}
}
//defining an interface called IDestroyable
interface IDestroyable {
//property to store the audio file of the destruction sound
string DestructionSound { get; set; }
//method to destroy an object
void Destroy();
}
//implementing the interface IDestroyable
class Chair: Furniture, IDestroyable {
//implementing the interface's property
public string DestructionSound { get; set; }
public Chair(string color,string material) {
this.Color = color;
this.Material = material;
//initializing the interface's property with a value in the constructor
DestructionSound = "ChairDestructionSound.mp3";
}
//implementing the interface's method
public void Destroy() {
//when a chair gets destroyed we should play the destruction sound
//and spawn the destroyed chair parts
Console.WriteLine($"The {Color} chair was destroyed");
Console.WriteLine("Playing destruction sound {0}",DestructionSound);
Console.WriteLine("Spawning chair parts");
}
}
//implementing the interface IDestroyable
class Car: Vehicle, IDestroyable{
//implementing the interface's property
public string DestructionSound { get; set; }
//creating a new property to store the destroyable objects nearby
//when a car gets destroyed it should also destroy the nearby object
//this list is of type IDestroyable which means it can store any object
//that implements this interface and we can only access the properties and
//methods in this interface
public List<IDestroyable> DestoryablesNearby;
public Car(float speed,string color){
this.Speed = speed;
this.Color = color;
//initialize the interface's property with a value in the constructor
DestructionSound = "CarExplosionSound.mp3";
//initialize the list of destroyable objects
DestoryablesNearby = new List<IDestroyable>();
}
//implementing the interface's method
public void Destroy() {
//when a car gets destroyed we should play the destruction sound
//and create fire effect
Console.WriteLine("Playing destruction sound {0}", DestructionSound);
Console.WriteLine("Create fire");
//go through each destoryable object nearby and call it's destroy method
foreach(IDestroyable destroyable in DestoryablesNearby) {
destroyable.Destroy();
}
}
}
}