Interfaces and Extensibility

Mosh Hamedani
A free video tutorial from Mosh Hamedani
Passionate Software Engineer and Best-selling Author
4.5 instructor rating • 20 courses • 456,157 students

Lecture description

I'll show you how we can change an application's behaviour without changing its code, simply by using interfaces as extensibility points. I'll also introduce you to the open-closed principle of object-oriented programming.

Learn more from the full course

C# Intermediate: Classes, Interfaces and OOP

An in-depth, step-by-step guide to classes, interfaces and object-oriented programming (OOP) with C#

05:29:53 of on-demand video • Updated October 2019

  • Work with classes, constructors, fields, properties, methods and indexers
  • Use encapsulation to improve the robustness of the code and reduce the impact of change
  • Re-use code using inheritance and composition
  • Understand the problems with inheritance and how composition solves these problems
  • Change the behaviour of an application by extending its code, rather than changing it
  • Develop loosely-coupled, testable and extensible applications using interfaces
English In the last lecture you saw how we used interfaces to improve the testability of our application. In this lecture we're going to see how interfaces health with extensibility are application. So similar to The Last Lecture. We're not going to have any slides here. So let's jump into Visual Studio and start coding. OK let's if you like to create a tool for migrating a database we can start by creating a class called DB migrator class can have a singular method called migrate which can be void. And here we are going to have details of migrating the database now to keep things simple. I'm not going to implement that here but what I want to show you is how we can create extensibility points here by using interfaces. For now let's just assume that we would like to log some messages on the console as the database is being migrated for example we may want to display the time the migration started. You may display the time migration finished if there were any problems along the way. We can display them on the console. So one way to implement something like that is to go here and say all right line migration started at and here we can have the current date time potentially we can have another console in dot write line here to indicate where in-migration finished . Now the problem with this kind of implementation is if tomorrow we decide to use a file or a database instead of the console we have to come back to this method and change it. Which means this class has to be recompiled and redeployed. Let me tell you something interesting here. In an ideal world we would like to design or suffer such that we can change its behavior without changing its code. You might wonder how is that possible. Well it is possible by using extensibility which means instead of changing the code in the existing classes simply add in new classes that change the behavior of the system. With this way of thinking every time we want to change the behavior of our solver we create new classes . And as you learn from the last lecture we can write unit tests or any kind of automated test only for those new classes. The impact should be minimal to zero on the existing classes because we are not changing the existing code. Now as much as this idea sounds beautiful sometimes it's a little bit costly to implement. So in practical terms we can not design software such that every change can be implemented using an extensibility mechanism. Well it is possible but sometimes the cost of that is way too much and it's really not worth it. But in this case in our DB my greater. It is possible that someday in the future we may decide to log messages in a file or in a database as opposed to a console. So how can we design this DD migrate such that it's extensible. Well that's by using an interface. Let's see how we can make this happen. So instead of using the console directly here we should use an interface like logger. Let's create an interface for that first so public interface. I logger. Not that I'm prefixing that with capital I know lugger may provide methods like log error which gets a message or another method like log info is for simply logging information messages which are not for reporting problems in the code. It's purely for troubleshooting. Let's put this interface in a seperate file to clean up our code. So back here alternate enter enter. OK now back to the program and I'm going to do the same thing with DB migrator I'm going to put it in a separate file so alternate enter enter. Now we want to change DB migrator to get and I logger interface. So as you learn in the last lecture we need to create a constructor here and in the constructor we inject that interface. So I lugger the colored logger and with resharpen we can press alternate enter here when the cursor is on the logger here and enter again and resharpen automatically create a private read only field of type. I lugger So the technique you are using here is called dependency injection which means in the constructor you are specifying the dependencies where it is DB migrator class and later in the main method as you will see shortly. We're going to specify a concrete class that implements that interface. So this whole thing is called dependency injection. Now in the migrate method instead of directly talking to the console you're going to talk to a longer . So say logger that log info and I'm going to cut this message here. And paste it here except that I need to change it come on to plus because I have to concatenate them to form a string . We're asking the console dot write line. We were passing multiple arguments. Let's remove that. And I'm going to repeat the same thing here. This time I change started to finished again to refresh your mind remember the analogy I use for the restaurant. They said the restaurant needs a chef. Now whether that shift is Jonge or Steve or Mary it doesn't matter as long as that chef has some capabilities to a given standard. It's the exact same scenario here so DPMI greater doesn't care who is the actual logger and instance of any class that implements this interface can be passed in the constructor here. So there is no coupling between DB migrator here and that concrete class. Now let's create a concrete class that implements that interface. So the public class let's call it console logger because we use console for logging I lugger not the red underline that's because we have not implemented the interface yet. So we press haltered enter and enter. Now here I'm using resharpen. So it opens up the dialog box which allows me to choose which methods I would like to implement. And by default both of them are checked. If you don't have resharper I think visual studio automatically implements all methods in the interface . So let's just go with the defaults. So for long an error I'm just going to use console dot write line and pass that message here and to make it a little bit more user friendly. We can perhaps change the color of the font so say Konsole foreground color equals Konsole color dot red to indicate an error. We can't repeat the same thing in the log in full method and it's sort of using red. We can use say green. Now let's put this class in a separate file. OK let's take one final look at DB migrator. So look it knows absolutely nothing about the console. And it's just talking to an interface. Now let's go to the program in the program. We're going to create an instance of DB migrator. So our DB migrator equals new DB migrator in the constructor. I need to specify a concrete implementation of that. I love our interface. In this case we pass a console logger. And finally we call them migrate method of the DPMI greater let's run the application. There you go. We got two messages on the console migrate -iong. Sorry for the spelling it's migrating started at. Here is the date time and we had a problem here with the formatting of string. It doesn't really matter. And you see the second message migrating finished at the time. Now let's say the hypothetical scenario is going to happen. So we decide it's full of luggin messages on the console. We decide to log them in a file. Let's see how we can achieve that. So all we have to do is create a new class Pawlick class lugger. It implements the I logger interface and repeat the exact same steps here. So all 10 enter and enter again and again we got this method here. So let's create a constructor that takes the name of the file. So the tour area passes better and we press alt and enter and again so we got the private field here and in the log error method we need to create a stream writer or writing data to a file. So News Stream writer know that this class is defined in System done. Io namespace as you see here. And because I'm using sharper resharpen automatically either the using namespace for me here so it makes your life much easier. Mike here now in the constructor or stream writer I need to specify the path. So I use that private field and the second argument specifies whether you like to append for that file of course. Yes. Then we can call dot write line method and pass that message here. There is just one problem here. The problem is that this stream writer uses a file resource. A file resource is not managed by C L R or a Common Language Runtime which means we need to dispose that resource once we finish using that . The easiest way to do that is to wrap this whole block here in a using statement. So let's see what happens. I moved this large stream writer inside using. And then in the using block I move this stream right writer dot write line here. Basically what happens behind the scene is there is an exception handling mechanism inside this using that you're not going to see it's implemented by the compiler. So if something goes wrong an exception is strong or something the compiler will make sure to close the file handle by calling the Dispose method on the stream writer. Let me share a method here so the stream writer do suppose this method is used or fring external resources that are not managed by C-L or when we use the using statement here the using block the compiler automatically includes a call to the Dispose method so we don't have to manually do that. OK one last thing here. Perhaps it's better idea to add a prefix string here to specify that this is an error because when working with a file we don't have colorists like as we did with Konsole so we can't say error plus message could potentially include the date time here as well but it doesn't really matter for the purpose of this lecture. Now interestingly we need the exact same code for the log info method here but look we are duplicating code and there is a principle called dry or don't repeat yourself. So we can refactor this and make it a little bit better what we can do is we can create a private method call it log it gets a message and perhaps get another string like message type. Let me scroll down so you can see just a little bit better. And then I'm going to move all this code inside this method instead of using this hard coded error prefix here. I'm going to use message type plus colon and a space and then I'm going to reuse this method in both log error and log in for methods. So there's no code duplication anymore. So I'm just going to call log past a message and the message type is going to be error. I would do the exact same thing here with log info. Now even a better way to do this is to use any anti-immigration. So you should be familiar with enumerations. But again I leave that to you as an exercise because I don't want to get too distracted with that coding practices in this lecture because we just want to see how we can use interfaces to create extensibility points in our application. So I think for now I should do the job. Let's put this class in a separate file. So I put the cursor here Hultin enter and enter. Good. Let's review what we have done so far. So back to our program. See created a DB migrator and passed a console logger instance his constructor. And finally here we called the migrate method. Let's take a look at DB migrator so DB migrator talks to an interface. It doesn't know what concrete class is going to implement this interface. It doesn't really care. Now at this point when we run the application messages are logged on the console. We can't change the behavior of the application. We're simply going to the main method and swapping that implementation with a different implementation . So instead of Consta logger I'm going to use file longer specifying path. So I'm going to use C. Kallen backset backslash. A logged RTX XTi. Actually this may throw an exception of my machine because I'm running this in a virtual machine and I don't think there is access to the root of C drive so I'm going to change the path to projects back slash back slash log dot txt. Let's run this well that no messages are logged in the console anymore. Let me go to the file system . So C-Drive projects Loek. We have a log file here. So in four messages are logged in this file. You'll see there are four of them because I ran the application twice in the middle of recording. So don't worry about that. There is no bug. The first one says migration startup migration finished. And then here we run that application again. So we got two more messages. So the interesting thing here is we changed the behavior of our application by simply creating a new class as you saw here by logger and simply passing that to this constructor here. I did not change one line of code in DB2 my greater and this is what we call changing the behavior by extending application instead of changing the code in object oriented programming. This is referred to as open closed principle or OCP which states suffer entities should be open for extension but closed for modification. And in practical terms that means here this class should be closed for modification. We didn't change anything here but this is open for extension using this extension point. Now don't think that you always need to use an interface in scenarios like from console to file system or from file system to database. Not necessarily. That was just a simple example in your application you might have an application that is working on a map like a GPS obligation like Google map and you need to calculate the shortest distance between two points. You can use an interface like Route calculator that finds the shortest path between these two points . Later in the future you may come up with a better algorithm maybe an algorithm that is faster maybe an algorithm that knows the traffic conditions or whether Street is one way or two ways. You can't simply create a new implementation of that interface and inject that to your class without changing the existing code. Or let's say as another example in your application you have some kind of encryption Maybe you have a class that needs to use an encryption service. So in that case you can create an interface call or encryptor and create a class that implements that interface with a very basic encryption algorithm. Later in the future you may come up with a better algorithm a better encryption algorithm and you can simply change the behavior of your application by creating a new class that implements that interface . So you've got the point. Well that's pretty much it for this lecture. I hope you enjoy it. And thank you for watching