Today, I want to talk about interfaces. More specifically, what they are and more importantly when to use them, as this point is often forgotten and/or misunderstood.
You have most likely read/learned that an interface is some sort of contract. To provide some more context, a possible definition you might find is: An interface is a contract that guarantees to a client how a class or struct will behave. Even though it’s correct, what do you really understand from this statement? Let’s try to better understand it.
Interfaces in Real Life
First, let’s break the programming context and think of an interface in the general meaning. One of dictionary.com’s definitions of an interface is a common boundary or interconnection between systems, equipment, concepts, or human beings. The keyword here is BOUNDARY. Keep this in mind.
Let’s take an example: the power outlet. Think of any devices (such as TV, phone, tablet., oven etc.) which require electricity to function as intended. All these devices have a power cable and a plug which needs to be connected to the power outlet in order to get the required electricity.
On the other side, the electric company, needs a way to transfer the electricity to your home.
Which is the boundary between your devices and the electric company transferring the electricity to your home? Exactly, the power plug.
Interfaces everywhere
Please note that there are multiple types of plugs based on the country standards. So there are multiple interfaces for the same purpose (powering devices). Your plugs need to respect the correct interface in order to be powered up, otherwise you won’t be able to use them (unless you have an adapter, but this is a discussion for another time).
Now, think of your phone. You’ll have a charger for it to plug it in an outlet. But your phone can also be charged from a power bank or from your computer via USB. In this case, USB is another interface. All phone chargers have the plug which respects a certain interface (e.g. schuko). An USB cable is connected to the plug to power the phone. This way, you can use your cable to charge your phone through USB (via laptop, powerbank etc) or a power outlet. Of course, you have multiple devices which use the same interface (e.g. schuko plug) for the same reason (powering up devices).
If you think about it, the other end of the cable goes into your phone (for example micro usb or lightning bolt). This interface serves as both data transfer and charging capabilities, right?
Let’s take another example. When driving a car, a minimum you’ll need is a steering wheel, a gear shifter (manual or automatic), some pedals. These operations represent the way you interact with a car, how you drive it, the interface between you and the car.
Let’s think from a different perspective. You have some products to sell and some people want to buy these products from you. The interface between your products and your customers can be a seller. You can do it by hiring a person to sell products or do this by having an online store or both. Either way, the main responsibilities of the seller will be:
check what products the buyer wants to purchase
calculate the price for those products
collect the payment for those products
give purchased products with receipt to buyer
Another interface is the audio jack you have on your phone and other audio devices.
As you can see, whether you like it or not, you are surrounded by interfaces.
Interfaces in programming
Let’s get back to the interfaces in programming. So what are they in this context? They are basically the same thing: a way to define a common form and/or behavior which will be used by multiple objects. The form and behavior are only defined by the properties, methods and events and are NOT actually implemented in the interface.
Let’s assume you are creating a product which will exchange currencies at the best rate in the country, but your product is not yet implemented. You have several partners which have other software products in development and want to integrate with your future product.
One approach would be for them to wait for you to finish yours. However this might lead to unnecessary delays. So, instead, you could define a (public) interface you will use to allow an integration between your product and your partners products. As long as you all commit to this interface, the development on all products can be performed in parallel. Of course, some synchronization points between the parties will be required in order to make sure everything is working as expected.
Having an interface has several benefits. One of them is allowing development of inter-connected products in parallel.
If we go one level of abstraction lower, you could use interfaces only in your own code for defining a common way for working with different objects which have the same behavior (polymorphism), thus simplifying code logic and making it more clean and maintainable.
Going one level of abstraction even lower, you’ll see that any class has a “default” interface which is defined through the public access modifiers. Through these access modifiers of object X, other objects will be able to integrate with object X. This last example goes beyond the scope of the actual interface, but it’s basically the same concept, just defined differently.
Another benefit of such interfaces is that you can easily mock them in unit tests. Let’s assume we have the above case. If I were to implement my product based on an interface provided by someone else, I will be able to mock the provided interface and return whatever I want on various methods to make sure MY code works as expected in all cases, without having to rely on real data.
For example, let’s say I have the IExchange interface with the GetRateFor(string fromCurrency, string toCurrency, int amount) method. My application will use this interface to calculate the final price in the local currency (RON). So I would make a call to the the product you are developing with something like GetRateFor(“EUR”, “RON”, 1) and your response today would be 4.76. So now I would multiply the price of my product, lets say 100 euros to 476 lei.
But, given your application is not ready and I want to continue working on my application, I can mock the application in my unit tests to return whatever value I want (4.76, 0.5 or -10 if I want to). This way, I can test my application can handle various responses from your application. Furthermore, my tests will be much faster since I mock your interface and my expected value will be returned immediately compared to going through the internet to your application, making a request and awaiting for your application to provide it.
Imagine having something as 500 ms delay / request, which you might think is pretty fast. Now imagine having tests which perform 1000 requests. That’s over 8 minutes of wait time. Mocking most of these requests, will cut down the execution time to almost zero. I say mocking most of them as you would like to have some real calls to ensure everything works as expected in the integration tests. More about unit tests and integration tests in future posts.
From the C# perspective, an interface:
is defined using the interface keyword
is public
contains definitions (no implementations) of methods/members/events. By definitions I mean names, parameters etc.
is used by a class by using the same operator as for inheritance – colon (:)
cannot be instantiated
usually uses a naming convention which prefixes the interface name with an I. For example if we have the Exchange interface, the actual name of the interface will be IExchange
Let’s say we have class Invoice which uses interface IExchange (Invoice : IExchange). We don’t say that class Invoice inherits interface IExchange (even though uses the same : operator as inheritance). We say class Invoice implements interface IExhchange. This would mean that all members/events/methods defined in IExchange interface will need to be implemented in Invoice class. Furthermore, a class can have implement multiple interfaces. For example, we could have Invoice : IExchange, IEnumerable.
This is where the term contract comes in place. An interface defines a “contract” (e.g. public methods/events/members), which the class implementing the interface is forced to implement.
When to use them
Ok so now that we know what an interface is, let’s see when to use it and when to NOT use it.
As I said earlier, the keyword in the definition of an interface is boundary. You are encouraged to use an interface at the boundaries of your application.
Where are those boundaries? Everywhere your application does something over which it has no direct control (because it’s being managed by another system): registry files (managed by operating system), file system (managed by operating system), databases (database engine – e.g. SQL Server, Mongo DB, Oracle DB), other 3rd party resources (web services, other programs) etc.
As I said before, using interfaces in such cases will allow you to mock them and ensure your program will handle more response types compared to the one the 3rd party application is currently giving you and also speed up your test execution.
Another case where you might want to use interfaces is to define a common behavior of two or more classes in an polymoprphic way.
Some people abuse the usage of interfaces and end up in having a pairs of classes and interfaces. In my opinion, despite of having everything super de-coupled (which is not always an advantage), this is not a very good idea because it brings complexity to your project. And as we know, complexity kills projects (see Code Complete).
Having all your classes doubled by an interface with the same name will also make you “blind” to cases where an interface is used for the other two reasons stated above. As a result, you’ll have a harder time finding interfaces you might want to re-use and end up re-implementing an existing interface in a different way (even multiple times if project is big enough and lots of people with various business knowledge levels work on the implementation).
Again, think about the phone charger scenario. You could make different phones, each having it’s own special charger. Even though you can work on the phones in parallel without having to worry about the charger and making sure a potential error at charger level will not propagate to other phones, on the long run it will complicate things. You’ll need special adapters for every phone you made, and you need to have them with you at all times. See also Martin Fowler’s opinion about this approach, he’s appreciated in the software development world and also an author of various books.
To recap:
an interface is a way of defining a form and/or behavior to work with an object
an interface cannot be instantiated
all interfaces have public members/events/methods
interfaces usually follow the I prefix naming convention: Exchange interface => IExchange
a class implements an interface (does not inherit it)
all classes which implement this interface will be forced to implement all the members/events/methods of the interface
As always, if you need help or have questions please don’t hesitate to contact me via Facebook or drop me a line. I will follow up on those, I promise.
Comments