Interfaces are a powerful piece of object oriented programming that is often overlooked. In this, the very first episode of Coding Blocks the Podcast, we discuss many of the things you should know about interfaces.
To help follow along with the podcast, here are some code snippets to explain some of the conversation. In regards to Joe’s statement about interfaces being like guard rails on a highway, putting some code snippets in here should give some context and greater understanding.
So what exactly does that mean, guardrails?
First, what we’re demonstrating here is a factory type implementation where we have an employee class that has an internal class of workers. By forcing the internal class to contain all the code, we now keep programmers who use this class from calling anything they should not have access to. For example, we don’t want a regular employee instance being able to log in or out and we don’t want that instance to have access to an hourly rate. Now when you create a new “TemporaryWorker”, you’ll only have access to the restricted set of functionality those instances possess – thus giving you the guard rails. If you try to set the salary of a temp worker, you’ll receive a compile time error as it’s not available.
1 2 3 4 5 6 7 8 9 10 11 12 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { interface IEmployee { string FirstName { get; set; } string LastName { get; set; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { static class Employee { public static ITempEmployee TemporaryWorker(string firstName, string lastName, decimal hourlyRate) { var emp = (ITempEmployee) new Worker(firstName, lastName); emp.HourlyRate = hourlyRate; return emp; } public static ISalaryEmployee SalaryWorker(string firstName, string lastName, decimal salary) { var emp = (ISalaryEmployee)new Worker(firstName, lastName); emp.Salary = salary; return emp; } private class Worker : ITempEmployee, ISalaryEmployee { public Worker(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get; set; } public string LastName { get; set; } public decimal Salary { get; set; } public decimal HourlyRate { get; set; } public void ClockIn() { throw new NotImplementedException(); } public void ClockOut() { throw new NotImplementedException(); } public void TakeBreak() { throw new NotImplementedException(); } public void TakeVacation() { throw new NotImplementedException(); } public void DoPeerReview() { throw new NotImplementedException(); } public void ChooseCareerPath() { throw new NotImplementedException(); } } } } |
Below are the interfaces used in the private Worker class above
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { interface ISalaryEmployee : IEmployee { decimal Salary { get; set; } void TakeVacation(); void DoPeerReview(); void ChooseCareerPath(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { interface ITempEmployee : IEmployee { decimal HourlyRate { get; set; } void ClockIn(); void ClockOut(); void TakeBreak(); } } |
So now you’re probably wondering, well why would I do that rather than just create an Employee class as a superclass/base class, and then just create subclasses for TempEmployee and SalaryEmployee? Well, you could, but, now, let’s suppose that you’re going to have another type, we’ll call it HourlyEmployee? It shares some of the features of both a SalaryEmployee and a TempEmployee, specifically an hourly rate rather than a salary, but the hourly employee also gets to take vacation…so now we’re mixing and matching the functionality between the two other implementations. To do this using a subclass, you’d basically have to implement the code for paying hourly employees twice, once for the TempEmployee as well as for the HourlyEmployee. Also, you’d have to implement TakeVacation twice as well.
Considering that both the hourly pay functionality and vacation are likely handled exactly the same, this could be accomplished through an interface much nicer. Check out this new interface along with the updated Employee.cs file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { static class Employee { public static ITempEmployee TemporaryWorker(string firstName, string lastName, decimal hourlyRate) { var emp = (ITempEmployee) new Worker(firstName, lastName); emp.HourlyRate = hourlyRate; return emp; } public static ISalaryEmployee SalaryWorker(string firstName, string lastName, decimal salary) { var emp = (ISalaryEmployee)new Worker(firstName, lastName); emp.Salary = salary; return emp; } public static IHourlyEmployee HourlyWorker(string firstName, string lastName, decimal hourlyRate) { var emp = (IHourlyEmployee)new Worker(firstName, lastName); emp.HourlyRate = hourlyRate; return emp; } private class Worker : ITempEmployee, ISalaryEmployee, IHourlyEmployee { public Worker(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } public string FirstName { get; set; } public string LastName { get; set; } public decimal Salary { get; set; } public decimal HourlyRate { get; set; } public void ClockIn() { throw new NotImplementedException(); } public void ClockOut() { throw new NotImplementedException(); } public void TakeBreak() { throw new NotImplementedException(); } public void TakeVacation() { throw new NotImplementedException(); } public void DoPeerReview() { throw new NotImplementedException(); } public void ChooseCareerPath() { throw new NotImplementedException(); } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System; using System.Collections.Generic; using System.Text; namespace Interfaces { interface IHourlyEmployee : IEmployee { decimal HourlyRate { get; set;} void TakeVacation(); void ClockIn(); void ClockOut(); void TakeBreak(); } } |
Now, as you can see, with very little additional work, we’ve got an Hourly Employee that has all the functionality that’s already available. SWEET!
Above we’ve demonstrated a number of features of Interfaces. We have all of our Interface classes implementing an existing interface, IEmployee, and then, in our Employee class, we can see that the internal class Employee is implementing our three other interfaces. Considering that the functionality, in theory, for our employee class all does the same thing regardless of the type, you can see that we’ve rather easily created a “guard rail” or a “can do” contract that let’s our developer easily work with the various instances.