SOLID Design Principles in C# and .NET | Dmitri N. | Skillshare

Playback Speed

  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x

SOLID Design Principles in C# and .NET

teacher avatar Dmitri N., Quantitative Finance Professional

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

7 Lessons (56m)
    • 1. Overview

    • 2. Single Responsibility Principle

    • 3. Open Closed Principle

    • 4. Liskov Substitution Principle

    • 5. Interface Segregation Principle

    • 6. Dependency Inversion Principle

    • 7. Summary

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels
  • Beg/Int level
  • Int/Adv level

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.





About This Class

This class teaches you the SOLID design principles using the C# programming language. The SOLID principles include:

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

In this class you'll learn about these principles and see sample code that teaches you how to spot when these principles are not adhered to and how to fix the situation.

This class is part of a broader "Design Patterns in C# and .NET" curriculum.

Meet Your Teacher

Teacher Profile Image

Dmitri N.

Quantitative Finance Professional


Class Ratings

Expectations Met?
  • Exceeded!
  • Yes
  • Somewhat
  • Not really
Reviews Archive

In October 2018, we updated our review system to improve the way we collect feedback. Below are the reviews written before that update.

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.


1. Overview: Hi there. In this section of the course, we're going to take a look at the solid design principles. As you may have guessed, Solid is actually an acronym or abbreviation, and it stands for the five different principles that we're actually going to talk about. So these all the design principles were introduced by Robert C. Martin in Hiss, Siri's off books on object oriented design and agile practices and all that. So in the sea Shop land, the book to go for is agile principles, patterns and practices in C shop. Now, personally, I cannot say that I would recommend this book. I find it a bit long winded, and also the descriptions of the design patterns aren't notice deep as I plan to do in this particular course. But this is the book where you would find the solid design principles and of course we're going to cover them in this course as well. So, uh, what you find, though, is that these solid design principles are frequently referenced in design pattern literature and also, in general, object oriented design. So people use terminology from the sort of design principle. So I think it's worth knowing and worth being aware of this. Ah, these constructs before you actually jump into learning design patterns 2. Single Responsibility Principle: the single responsibility principle is just a piece of very good advice on how to build systems. And it specifies that any particular class should have just a single reason to change. Now I know that sounds cryptic, so let me give you an example off something that you might build. Let's suppose that you want to make a journal where he write down your most intimate thoughts. So you make a classical journal and you have a bunch of entries in the journal, so we'll have a private read only list string off entries in your journal that's gonna be initialized so that we don't get any no reference. Exceptions were also going to keep account of all the elements, so private static into account equals zero. Now, given that we have this Jonah, we might want toe add Andries to this journal so I might have a method called ad entry like so. And here I'm going to add an entry, which is just a bunch of text, and what I'm going to do is I'm going to say entries dot and let's say I want to have the number off the entry. So plus bus count here and I'm going to have the entry itself. That would be the tax, and I will return also the actual position off that particular entry. By the way, that's a memento pattern that we're going to take a look in quite a while. So in addition to a having this ad entry, you might also want to remove the entry using that index. So we have something like public void remove entry, which takes an index like so. And all we have to do here is, say, entries dot removed at and specify that particular index not a very stable way of removing entries, by the way, because once you remove one of the entries, all the indices that you have to the other elements become invalid, which is something worth correcting later on. But anyways, this kind of functionality is relevant to the Journal. You might also want to generate some sort of to string implementation. So let's do that. So I'm gonna override to string, and what I'm going to do is I just join all the entries with a line breaks. I'll say string dot join environment New line and have the entries here. So these are my entries and I I can start working with them so I can make a journal. So here, down below, What we can do is we can say Bar J equals new journal. We can add a bunch of injuries so I can say Jadot and entry. By the way, I hope I didn't make it private. So Jadot and entry, let's say I cry today Very sad journal and then another entry. Let's say I ate a bug. So these are the two entries them we can now write line right line the actual entries So we can say right line J that will call to string, obviously, and we can execute this and hopefully everything goes fine. We get our output fingers crossed. Okay, so build succeeded. And here are the two outputs. Now all the methods inside journal actually relate to the journal. So we're adding renters in the journal. We're removing entries, and in addition, where kind of printing them to ah, the command line or whatever, We're just out putting them to a string. Now let's suppose that you also want to persist. The journal. You might want to save it to a file so typically the kind of things that people might do. He's actually stopped making methods right here. So you make a method called Save, and this safe would take maybe the file name. You could also have Ah, different kinds of parameters are here. You would do something simple like, ah, file, daughter, write or text or something to that effect. So filed out. All right, All text, like so he would write the text to the file name and you would call to string toe get the actual text, Of course. So this is the kind of stuff you might want to start adding. You would similarly have a move called Load, which would actually also take a file name toe load from. And it might actually be a static method that returns a new journal. So it might be a static journal load kind of implementation. In addition, you might have another implementation which actually loads from what you are. I so you might have something like this. Now, the problem here and the reason why we're violating the single responsibility principle is that we're adding too much responsibility to lead journal class. You can see that the Journal is now responsible not only for keeping the entries but also managing all off this persistent. So you might have more than one reason to change the journal, not just to change the way that injuries are stored or the functionality for removing them , for example, because what I've implemented here is not particularly efficient. But in addition, you might also have additional reasons to change this when persistence changes. So this is something you want to avoid, and this is something that the single responsibility kind of mentality should hopefully get you to not do. So the idea here is that instead of doing this, what you would typically dio is you'd make a new class which handles the persistence of things. And this is where you would, for example, make a method called safe to file where you can save all sorts of objects. But you can also save journals. So you would take the journal, you would have the file name you would maybe half a Boolean flag for overriding false by default. And then this is where you would do the actual implementation. So if somebody wants to override or there is no such a file, there is no file within a file name. Then you go ahead and you filed out, right? I'll text you, write all text with a fire name, fire name. And once again, Joe no dot to string what we call the J. So Jadot to string. And that's how you would serialize the whole thing. So now we need to classes for persisting a journal, not just the journal itself, but also this persistent class. But the consequence and, ah, the kind of side effect of this is that, you know, have a separation off concerns. So the Journal is concerned with keeping a bunch of injuries. And the persistent class is concerned with saving whatever object that's being fed, whether it's a general off something else to a file or maybe to some Web stores somewhere. So you would save r P equals new persistence, persistence like so And then you would specify the file name so far, while name equals and then some file name. So it's a C temp. Ah, journal Doc Txt. And then, of course, you can actually say p dot safe to file. You can are specify of the journal, the file name and Maybe if you want to override, you specify True. And once this is done, we might want to display what actually went on in there so we can save process. Don't start so we can just look at the file name while name and see that it actually works . So if I execute this, you can see that the file has been saved. Here's journal dot txt And here is its actual tax show contacts. So everything works just as we expected. So the whole point off the single responsibility principle is that a typical class is responsible for one thing and has one reason to change. 3. Open Closed Principle: Okay, So we're now going to take a look at yet another principle from the solid principles. And this one is called the open closed principle. So what is this all about, Will, Once again, I'm going to set up a scenario where we're going to violate the open, close principle. And then we're going to rectify this and get the implementation, which actually doesn't break open. Close. So let me, first of all, show you a very simple DeMann. Ah, let's suppose you're building a soldering system, maybe some website way you can buy products and the products have certain categories and certain traits. So, for example, you might have different colors of products, so you might have red, green, blue and so on products, and you might simply have an enumeration for these size of the product. How big it is. So you might have small, medium large may be huge if you're Donald Trump fan. So now that you have this set up, you might have a class which actually implements these particular traits or not so much implements them as uses them to describe a particular product. So we're gonna have a class called product, so the product might have a name might have a name like so you might also have a color, and you might have a ah size as well. And I want to keep all of these public, by the way, so I'm not making properties and set out just making public fields, which is completely legal in dot net, as you may know, so let's make a constructor where we initialize all of these fields. So this is the name, the color on the size, and that's it for the product. Not let us suppose that somewhere on our wonderful website, we want to be able to filter the kind of products that we actually get to see, and instead of using link to filter out a collection of products were having to restrict ourselves to Onley, filtering by certain criteria, kind of like Amazon, which doesn't let you filter by the actual rating anymore. So we're going to restrict of the kind of filtering that you can do by using a product filter. So let's suppose that your boss comes to you and you says, Well, how about filtering by size, for example? So can you please ensure that customers can filter the products by size, so you implement a method for doing exactly that. You say public static. I innumerable off product, please filter by sighs all by color. Doesn't really matter. Let's have filter by size. So you specify a bunch of products. I innumerable off products, which is the products, and you specify of this size that you want. So the obvious output of this is that you go through each of the products. So for each of RP in products, you say if P matches in terms off the size of Peter, size is equal to size than you'd return. P. That's really pretty. My shit. Now, would this kind of set up? You can already start using it for certain things in the sense that we might want to do a scenario now. So I'm gonna make a couple of products. I'll make an apple, which is going to be a new product which is called an apple. Obviously it's color is green and its size is rather small. Okay, in addition, will have its half a tree. You might go to a gardening store and buy a tree, so that's going to be a new product on the string. So I was gonna be a product called The Tree, and it's also green, but its size is rather large, and in addition, will have a house which has yet another product like so. So that's a house. It might be blue instead of green. So I was gonna be a blue and also a large item. Okay, so these are the three items we can make an array out of them or some other kind of I innumerable. But I'm gonna go with an array here, so we have a product array called products, and that's what we're gonna have the apple tree and house. And then, of course, we want to start using that filtering functionality. So I make a product filter of RPF vehicles, you product filter, and let's bows that I'm looking for all the green products. So what I can say is I can write line, for example, green products, and that's gonna be I'm going to say old, because that's gonna be the old way of doing things. But you'll notice that so far we don't have any filtering by color. We only have filtering by size, which isn't what we want. So in order to implement this new functionality, we unfortunately have to go back into the filter and we have to modify the filter. So we have to take this code weaken, duplicate this, and we need to filter by color this time around, so filter by color. And here we specify collar color. And then, of course, this JAG becomes P don't color is equal to color. There we go. So now that we have this, we can actually start using it on the products that we say for each of our P in a p f don't filter. So in this case, I have to make it public are actually it is already public because it just has to be filtered by color here. So we specify the products and we specify the color as green, for example. So we want to find all the green items. And when we do find a green item and by the way, let's see what's going on here. So we make it Oh, it's non static. Let's keep these things now static for for now and I'll get rid off. Ah, this thing here is what was really up to you can be static if you wanted to. So for now, we filled her the items by color, we can write line the information about the items are just put a dash here. So I'm going to say pete dot name is green. How we can actually execute this and see what we get. So we're getting the right hand put apples, green trees green. Everything is fine now. Unfortunately, what happens now is your boss comes back on the ball. Says, Well, now you're going to filter by both sides and color. You're gonna let people specify both the size and the color you want to such items by. So this is a bit of a problem, because now, once again, you have to open up product. Felder. You have to jump into that class, and you have to add yet another piece of functionality. So this time around, it's not just a simple duplication and renaming it's a bit more so we're gonna filter by size and color. And if you want to filter by size and color, you have to specify size, size, color, color, and then you say Peter size is equal to size and Peter Color is equal to color. So this is how you implement this. And then, of course, you can start using it. And this whole thing, everything that I've showing right now happens to break the open close principle. Because the open close principle states that classes should be open for extension, which means it should be possible to extend the product filter. It should be possible to make new filters, but they should be closed for modification, which means nobody should be going back into the filter and actually editing the code, which is already there, because we can assume that the filter might have already been shift to a customer. Now, how can we extend things without actually going back and changing their bodies? The answer is, of course, inheritance. The answer is that you can implement interfaces, thereby extending the capabilities off the system. So instead, off having this brute force approach, what we can do is we can implement a pattern and there's gonna be a new thing for us. We haven't actually done any kind of patterns before, and now we're going to implement a pattern, but not a gang of four pattern. Instead we're going to implement what you might call an enterprise pattern called the specifications pattern. And this is precisely the thing that will allow us to avoid violating the open close principle. So how are we going to do it exactly? Well, instead, off having this rigid functionality, we're going to make a bunch of interfaces, So let's have a bunch of interfaces, and the first interface that we're going to make is going to be called I specifications. Now I specifications implements the specifications pattern, which basically dictates whether or not a product satisfies some particular criteria. So you can think of I specification as a kind of predicated, which operates on any type t. So notice I'm keeping my options here. I'm basically saying that, you know, I specification can work on virtually anything. So here I can specify the only method which will be required by anyone implementing I specifications, and that is a method called is satisfied. So what's happening here is with saying that we allow people to make specifications and we check whether ah, particular item off type T is actually satisfying some criteria and that we're not specifying the criteria here. We're going to do it in just a moment. Now the other part of the puzzle is another interface, and this one is going to be called on I filter. Now, an I filter is precisely a filtering mechanism, but it's a filtering mechanism which once again operates on any type t. So we take a bunch off items of type T and with filter according to the specifications in which is defined here. So the interfaces that you return an eye innumerable off t and you basically filter a bunch off I innumerable off the items by innumerable off T items, given the appropriate specifications, given I specifications off T, which I'm going to call spec. So this is the interface. You feed it, a bunch of items you tell it, one the specifications off those items should be and how to fill to them, and you get a bunch of filtered items back. So now we can implement everything that we've done up until now, using I specification and I filter as well. So let me show you how to make a specifications for color. Let's suppose you want to filter items by color, so you make a color specifications because it's a very simple thing. This would implement an I specifications. And once again, the type here can be virtually anything. But we're filtering products, right? So we are working with products were not working with something else, although we might be. So we say that we implement I specifications off product. Here we go. So let's go ahead and implement the missing members. And here we need to have a definition for the color we're going to filter by. So here I say color collar like so I can initialize it in the constructor and then to check whether the cause specifications satisfied. I simply return that Tito color is equal to color just like that. All right, so this is our color specifications. We can now do the same kind off green products filtering, but we can do it in a much nice away. So for that, we need another piece of the puzzle, which is, of course, a new functionality for filtering products. Because remember, we made this interface I filter, but we haven't implemented yet, so that's what we're going to do right now. We're going to make a better filter. So here's a class called Better Filter, which is an I filter off products because that's what we're filtering. So let's implement the missing members here now. The implementation off this particular class is very trivial because all we do is we go through each of the items in turn. And if indeed, the item satisfies a particular specifications. So if the specifications at we provide is satisfied by the item, I then we you would return that item. That's pretty much it. That's all you have to do to make the better filter work. But now let's take a look at how to actually use it. So far, B f equals knew better filter like So Ah, well, right line here that we're getting green products once again, but this time using the new filter. And then, of course, I'll do another for each of RP in, and here we use the better filters. Rbf thought we say filter, and then we have to provide both the items as well. Ask the specifications So the items is product as before, but for the specifications will make a new color specifications where we specify that the collar should be green and then we can output it just as we did previously. So just copy this over. All right, so this is an identical way. And, of course, it gives us identical results, which is exactly what we wanted. All right, so now you might be wondering. Well, okay, this was color. Now, how about color and size? How about filtering by both of these, for example, so first of all, we have to make a size specifications. So once again, I'll make a size pacification, which is an I specifications off product. And here just implemented missing members here. We specify the size. We might provide it in the constructor, for example. And then when you want to check whether it satisfied, you return t dot sizes equal to size. So that's fairly easy. And now what we can do is we can filter items by size in the same way that we've done here . So now, coming back to this question What if you want to filter by both size and color as well? Well, this is actually possible using a Combinator which is also going to be an eye specification off something or other. So that's take a look at how to build one, So I'm going to make a class called and specifications. So this and specifications and it's going to be a generic one is going to be an I specifications off t. So we're keeping things generic for that. We're not really doing anything in terms of categorizing and saying, Oh, this is going to be only for products and nothing else is going to be a very general thing . So the idea is that the and specifications will actually take to specifications like a color specifications and a size specifications, and that will end them together in the is satisfied. So one of the ways of doing it is you simply specify I specifications t first and second. You might want to make a constructor. Ah, which actually initialize is both of them. You may as well use some see shop. Seven features it here if you want, so you can sort of like this and for the 2nd 1 as well. So the double question marks And then, of course, when it comes to check in whether it satisfied you simply return whether the first specifications is satisfied by T and the second ah specifications, he is satisfied by t. All right, so having made this, let's take a look. And how we confined, for example, all the large blue items. So let's ah, are right line here large a large blue items like so and then for each var p in better filter I thought filled her. Okay, so now we need to make an and specifications. So new and specifications, which has to provide a name in particular, have to provide a type. And here we're gonna have the two arguments. So the first argument, and by the way, we forgot products as the first argument here. So the first argument, its products and the second is and specifications and we need to specify the color specifications here. Eso the color has to be blue. And in addition of the size specifications, whether size has to be large. So we're looking for a large blue items or a set of items. In fact, So this is how you would make a composite specifications using a Combinator, and here we can go through the items in, like once again, weaken right line the actual items so we can say Peter name is big and blue like so. And of course, when I run it, I get the house. So the house is big and blue. So this is being a demonstration off the open close principle. Just as a recap, the open close principle states that parts off a system or the subsystems have to be open for extension. So you should be able to extend the functionality off a filter, for example. But they should be closed for modification. So you shouldn't have to go back into better filter, for example, and start adding things right here. You shouldn't be able to do that. Instead. If you want more functionality, you make new classes, you implement I specifications and you feed those into something that has already being made and something that may have already been shipped. You don't want to re ship the functionality of better filter to your customers, but you can ship additional modules which implement I specifications on which make use off better filter. So that is what the open closed principle is all about. 4. Liskov Substitution Principle: the next principal, but we're going to consider is the list cough substitution principal named off to Barbara Lisk off. So the idea here is quite simple. The idea is that you should be able to substitute a base type for a subtype. And once again, I know this sounds really cryptic and you might not be understanding what's going on. So let me explain. I'm going to explain using the classic demonstration off the list of substitution principle by using an example with just a bunch of rectangles and squares. And that's in the interesting, object oriented challenge, by the way. So let's suppose that we have a class called rectangle and we're going to define both the width and height. So to begin with, I'm just going to have them as automatic properties, all half order, probably called with, and similarly and other property called height. There we go. So now that I have this rectangle, I can make an empty constructor, I can make a constructor which initialize is each of the properties, and in addition, let's build some sort of to string implementation. Well, we can output the information about this particular object, so make some formatting members just to string, which will tell us what the rectangle is all about. So this is a fairly straightforward object to use, and you can go ahead and use it. In Maine, you can say rectangle R C equals new rectangle like so And then we might want to perform some action on a rectangle like, for example, calculating the area of this rectangle. So to calculate the area, I can say static public and area, which takes a rectangle, are and it returns are dot with multiplied by r dot height as simple as that. And what we can do now is we can output no, just some information about the rectangle but also calculate the area. So, for example, Ah, what I can do here is I can write line. I can write line that I have the erect angle, which is R. C in our case, and I can say rectangle has area, and then I can calculate the area using the area function that we've just made so ice cold area are see here and we can execute this and just take a look at the result to see that it's all actually working So here is the output we have with zero hide zero has areas. You're not very exciting. So I'll put some numbers here like two and three, for example. And we can go again. And this time to Demps three equals six. So everything is great. Okay, Now, this is fine until you decide to also implement a square class and you decide to inherit square from rectangle. Because why not? So here is square, which geometrically is in fact a kind of rectangle. So we say square is a rectangle. And then, ah, we need to decide how to handle the assignment to lead within the hide. Because remember, the square has to be consistently a square. You cannot turn it into a rectangle all of a sudden, or you could do it. But that's not what we're going to do. Instead, we're going to define new ah, with and height setters. So I suppose you go ahead. Then you write something on this public new into with and you specify a new center where when somebody changes the with, they changed the height as well. You can see this going wrong already. Count you. OK, so based up with equals based on height equals value, Terrible idea. And then the same goes for the height Public knew in height from where we set Ah, the based on with equals based on height equals value like so. So this might seem ah workable in principle and we can actually start using it than getting correct results. Surprisingly enough. So, for example, if we say ah, we make another EC tangle or let's say we make a square now so square ask u equals new square and we'll set Ah, these square with so as queued up with we'll set it to a value of four. And so we expect the area to be 16 if we write line this so right line Sq has area and we consider using the area function once again. So we called the area function with sq this time around. And if I execute this, you can see that we have correct operation. Everything is fine. Everything is absolutely fine. Why are we even discussing this string substitution principle? While the reason for this is that if you have a square than it is perfectly legal for you to store a reference to a square as a rectangle variable because remember, inheritance. Basically, a square is a rectangle, So I should be able to legally change this to a rectangle. And nothing should change, right? Well, wrong. We now have a height of zero and an area of zero. Now, I'm sure you can guess why this happened. This happened because when you're setting the with your only setting, the with that's it, you're not setting the height or anything. So the list cough substitution principal basically says that you should always be able to sort of up cast to your base type, and the operation should still be generally okay, meaning that the square should still behave as a square even when you're getting a reference to Iraq tangle for it. So the question is, Well, how do we fix this violation off the list of substitution principle and the fix is actually rather easy. All you have to do is you have to make sure that if there is in fact an override off with and hide, it is indicated as such because what we've done here is we've used the new key would. But instead what we can do is we could do something better. We could make the properties virtual so it could go up here and make each other properties virtual like so. And then, of course, instead, off new you would put override and over right here as well. So this time round, if we actually execute this, let me just show you that it's now working correctly again. What happens is even though you're holding a rectangle reference to a square when you go and access, the with what really happens is a walk over the virtual function table because it's looking at with. But it's saying, Oh, okay, let's look at the center. Oh, the center is a virtual. So how about we look at the V table and so it finds the appropriate center and calls the appropriate that are, in fact, on the square. So this is the list cough substitution principle 5. Interface Segregation Principle: All right. So the next principle which I want to show you, is the interface segregation principle. And for this one, I'm actually going to do a bit of a synthetic example because essentially the interface segregation principle is something that you are likely to encounter in the real world. Well, you just building into faces which are too large. And the idea is quite simple that your interfaces should be segregated so that nobody who implements your interface has to implement functions which they don't actually need. So let me give you an example. Let's because you have a document and you want to do different things with the document, so you try to emulate some sort of ah printer or scanner. But there is also this thing called the multi function printer, which does both the printing discounting as well as the photocopying as well. So you decide. Oh, I'll make an interface for everything. So you build one big interface called I machine, and this machine interface does all things so it can print the document so I can print the document on Documenta centers. I learned how to spell that is ah, print a document D and it can also scan the document scan and you can maybe fax the document as well. So you build this kind of interface and this interface works just fine, so long as you are actually working with a multi function printers. So if you have a multi function printer, then everything is fine because you implement this I machine interface, you go ahead and you implement the members and then you can do something for each of the members because the thing can in fact print it can in fact, scan documents and it can also fax document, for example. So this is okay, But then you go ahead and you decide to make just a good old fashioned printer, old fashioned printer. It cannot scan it cannot fax documents, anything like that. So the problem is that you've only got this one big interface with lots of responsibilities attached to it. So what do you dio? I mean, you have to implement this interface or you implement the interface I machine you implement the missing members, and certainly implementing the print function is obvious enough you have apprentice or you implement print. But now the question is what would you do about the scanning on the faxing? Do you throw an exception? Do you do and no up. Do you maybe outputs a message that this operation is not supported? I mean, the operation is kind of undefined a little bit, especially for the consumers off your A p I. So you would have to really document that your old fashioned printer, even though it implements the I machine interface. It does, in fact, only supply the implementation of print and does not supply them implementations off scant and fax. So that's a bit of a problem, and that is where the interface segregation principle comes in. So the idea is to make sure that people don't pay for things they don't need. And ah, this means that they don't need this scanning and the faxing than those things should be several. Somehow in instead of having one big interface, you should have lots of smaller interfaces. Which arm or atomic, shall we say, in the sense that they are, ah, kind of self contained and they cover a particular concern. So what you would do in this case is, of course, you'd make a lot of smaller interfaces. So for the printing, you would have an eye printer interface where you would have the print function, so this would work on a document, and similarly, for the scanning, for example, you would have the ice scanner into face, and this would have the void, Ah, scan function again on a document or into a document in this case. And then, of course, if you want to start implementing these functions than you can, and there are different ways of doing it, so one is if you are making a photocopier, for example, then you might want to implement just both of these interfaces. So if you have a photocopier, for example, then you might want to have the printer interface and also the eye scanner into face. And then you simply implement Aled the missing members, and you actually provide the appropriate implementation. Or, of course, if you do want a higher level interface as well, like it kind of I multi function device interface. You can have it as well, because interfaces can inherit from interfaces so you can have an eye multi function device , which is actually an interface, which is both an eye scanner I printer, my printer and, uh, other things as well so you can have other into faces here. And then, of course, when it comes to actually implementing this interface once again, you have different options of doing. It's a one way would be. If you have a class called multi function machine of some kind, you can just go ahead and you can implement this I multi function device interface. So that's one approach. And, ah, how you actually implement this is completely up to you. Because, for example, if you already have the implementations off a printer and a scanner, and you're saying that a multi function device is simply the joining of those two classes concrete classes that you already have, then you can do delegation. And in this case, you would implement the missing members. Or rather, you might not implement the missing members. What you can do instead is you can, ah, suppose you already have a printer in this counter implementation. Some way you could have, for example, on I printer cold printer and an eye scanner cold scanner on what you can do now, thanks to the magic of re Shoppers Festival. Ah, you can ah, make a constructor which actually initialize is both of these. And then, of course, what you can do is you can delegate the calls to each of the interface methods off my multi function device. You can delegate them to the printer and this kind of, respectively. It's the decorator pattern in actual fact. So what you can do is you can go in here into the generate menu. You can choose delegating members and just select all the members. And so print will be delegated to printer and scan will be delegated to scanner. So here this. So we've ordered generated the implementations off the eye multi function device interface , and we're actually delegating the coals to the colts off the inner printer and scanner variables. A classic example off the decorator pattern. So this is the decorator. So this is the end of a segregation principle, a basic idea that if you haven't interface, which includes too much stuff than just break it apart into smaller interfaces, 6. Dependency Inversion Principle: the dependency inversion principle is very simple. And it states that high level parts of the system should not depend on low level parts of the system directly that instead, they should depend on some kind off abstraction. So this might once again seem very cryptic. I mean, what does it mean to have high level and low level parts of the system while we're going to take a look at a scenario right now? So what I'm going to do is I'm going to model a genealogy relationship between different people, and we're kind of imagining a system where you perform queries on a genealogy database. So first of all, we may as well define certain relationships. A relationship between two people, like, for example, you can be a parent. You can be a child. I was gonna be anin. Um, by the way, not a class. So, you know, you could be a parent. You're gonna be a child. You could be something else, like a sibling, for example. So these are relationships that you can have between people, and then we can define person. And here I'll do a very simple definition. I'm just going to have the name of the person, although strictly speaking in the genealogy application, you would have other information as well. Like, for example, you might have the date off both, for example, so these sorts of things now I'm going to just ignore everything except for the name, just for the sake of simplicity. So now that we have this kind of set up, we may as well define the low level. And I'm gonna hear low level parts off the system by defining relationships between different people. And I'm gonna put them in in class called relationships like so. And here we're just going to have a flat list off relations between different people. So I'll have a private list. And now we use the C shop. Seven troubles here. So have the person from which the relationship stems the relationship itself and the person to which the relationship applies. So person, relationship, person as a value topple escort relations. And I'm just going to initialize it with a new list. Let's try and do it this way. So person relationship and person again. No, we go. So this is a set of relationships that we have out a low level and what we can do then on the low level, is we're going to find some sort of a p I for actually adding a parent and child relationship. So I'm going to do very crudely. I'll have add parent and child. And here I will have the person parent on the person child and now just duplicate the data , not something that you do in a typical database, but something that is very popular in today's design. When you just duplicate data for the purposes off speed of access. So I'm gonna say relations dot ad And then I'll at parent with the relationship off. Ah, parent, that should be relationship. Don't parent to the child and And of course, I'll add the reverse relationship just for the fun of it. So here you would have a child relationship dot child No child and that Ah, the parent, this is completely spurious. I'm just adding it to show that you can store things in this kind of de normalized way. Okay, so this is our low level a p I. And the question is well, how do we actually perform research on it? Want to find out all the Children off a particular person. Well, let's actually do this. Let's make a class called research. Now. In the main method, I'm going to set up a scenario. So I'm going to have a bunch of people have the parent as a new person with the name with a name John, for example. And then I'll have Children. So we'll have child one one, like so and child to here as well. Child, too. And I'll call them Chris and Mary. Now we go. So Joan has two Children, and we can define the low level relationships module, toe kind of income. Pass this information. So we present this information. We save our relationships. Relationships equals new relationships like so And then I can say relationships dot and parent and child sold parent child one and then duplicated child to There we go. So we've set up the scenario and that we actually want to perform the research. So we want to take the low level module and we want to somehow access it in a high level research module that we have here. So one of the ways not the best way, is to simply allow the high level module. The research Bondo access to some of the internals off the low level module by giving it the list off relations So you can do something like the following If we make a constructor . Ah, for research, we could have a constructive which actually takes relationships as the argument. We take the relationships as the argument we pass in relationships. And here so we say new research with relationships. And then, of course, Ah, what you want to do is you want to access all of the relationship information. So how do you do it? Well, one of the ways is you simply exposed this private field as a public property, and that's pretty much it. So in this case, what we can do is we can write something like public list off person relationship person. We exposed this list called Relations With a Capital R, as our relations like so OK, so we just made a probably getter for this field on. What we can now do is we can perform some sort of research by accessing this low level interface so I can say, Ah, var relations equals relationships. Don't relations like so I'm accessing that property that we just made. And then I can go ahead and look for all the Children off Jones so I can save our are in relations. Ah dot Where? And I can start filtering so I can say, Well, I need the name to be John. So I if we take access the argument I work extort item one don't name to be equal to John. And I want the relationship types of extra items, too. I want the relationship type to be our relationship, Doc. Parent, I want somebody for whom John is apparent. So then I can do a loop, and I can actually write line taken right line here that John has a child called our dot item three dot name. Okay, so this is a workable scenario. It will actually work if I'm a sort of compiling. Execute this. But there is a bit of a problem here that we need those references to value double. Of course. Since I'm using the C shop seven variety and we're still in visual studio 2017. I use value a couple to get my package in here. Now we go. All right, let's get those value topples in. And now hopefully, if I execute this I get my results of John has a child called Chris and another child called Mary. There we go. Now. The problem with this scenario and the reason why the dependency inversion principle exists , is that we're accessing a very low level part off. Ah, the relationships class. We're accessing its data store, and we're accessing it through a specifically designed probably, which exposes the private thing as public. What this means in practice is that relationships cannot change its mind about how to store the relationships. So the relationships class can't say, Oh, this is a bad way I'm using topples. I'm depending on this value type, I'm going to start using a dictionary and I'm going to remove this duplication as well or something to that effect. So it is very bad idea because then the relationships class can no longer really change in terms of the way it stores data. So a better way, which would allow relationships to change, is to provide a full of abstraction by simply defining an interface for how the relationships class actually allows you to access certain high level data. So, for example, I can define an interface called I Relationship browser so this contact will actually give us particular information like suppose I want to be able to find the Children off a particular person. So what I can do is I can put here a member which returns an eye innumerable off person and going to be called Find all Children off and you specify the name of the person whose Children you actually want to find. And then, of course, what I can do instead of, ah, this thing instead of doing this, which is horrible. I'm going to comment out This whole thing is what actually not just the lunches, the constructive, but let's calm it out. The entire body as well is I'm going to actually implement the interface that we've just made, which finds all the Children off a pretty good person. I'm going to implement this in our low level construct. So here I'm going to say, I relationship browser like, So I'm going to implement the missing members and hear. All I have to do is I have to basically replicate Ah, the kind of coal that I'm doing here, except it's obviously going to be slightly different. So if I just copy this over, let me just paste this. And here and how uncommon this as well. So what we're trying to do is we're trying to search everyone with the particular names instead of John. We put the argument which is name like so and then we simply ah Yun returned. Ah, the actual value, which in this case is our and we can actually simplify this because none of this is required. Well, it's not our It's our dot item two. That's the child item three. Rather, that's the child that we want to return. So I'm going to simplify this. Convert this into a basic Lincoln. There we go. So we now have a low level module. But instead of depending on this low level module, we depend on an abstraction, which is the I relationship browser. So what we can do in our research module, which is the high level modules weaken, build another constructor. But this time around, we don't depend on relationships. Instead, we depend on this I relationship browser interface called browser. And here if I want to find all the Children of John, all I have to do is say for each of RP in browser dot find all Children off John and I can once again right line. John has a child cold and then just say P don't name also looks better in terms of the A p I. And of course, if I execute this, I get exactly the same result. But I get it without the dependency on low level implementation to you though. So now what happens is relationships can actually go ahead and change the way that it stores the relationships that can change the underlying data structure because it's never exposed to the high level modules which are actually consuming it. 7. Summary: Okay, so let's attempt to summarize the things that we've learned in this section of the course. So we looked at the solid principles, and the first principle we looked at is the single responsibility principle. And that is the basic idea that a class should only have one reason to change. Now, this is something that also relates to an idea called separation of concerns, which is roughly the same idea. The basically different classes handle different independent aspect of the overall system. Though they handle different task, they solve different problems. And then, of course, you can combine them together. So once you have separation of concerns, what you can do is you can take all of those concerns and you can make them interact with one another. You can make them depend upon one another. And this is, ah, the kind of the object oriented part off composition. So you compose systems out off these separate concern solving task. So this is the single responsibility principle. Next we looked at the open close principle. Now, the idea behind the open close principle is that classes should be open for extension but closed for modification. What this means in practice is basically the following, its stating that if you've already got a class and this class has been written, it's being tested. You know that it works. It is really a bad idea to come back to this class and to add additional functionality to the source code of this class if you can avoid it. If you can somehow extent this class by using inheritance and interfaces and I don't know dependency injection, for example, then it's maybe worth not touching the original. So the whole open, close principle can actually lead you to design object oriented systems in a slightly different way in a way which is originally extensible by introducing interfaces, for example, and then implementing those interfaces in whoever actually wants to extend the system. And we certainly looked at an example of doing precisely this by implementing the specifications pattern in the example that I showed Next up, we had the list cough substitution principle, a basic idea once again that you should only be able to substitute the basic idea that you should be able to substitute a base type for a subtype because object oriented design requires that you should be able to sort of cast a descendant to its base and store it in a variable off type base. And it should still operate correctly for the most part, meaning that you might want to make certain things virtual so that you are accessing the correct members. So the no particular magic in the list cough substitution principle is just a basic idea that you are going to meet situations where perhaps the consumers of your a P I would try to access things through a base type, and they might encounter problems if you don't keep your own design off for your classes. Consistent Next up. We looked at the interface segregation principle, a very simple idea, by the way, that you shouldn't just put too much in and interface. And if you put too much in an interface, you're forcing the implemented off that interface to implement things. They don't need that, actually, also, it relates to the so called ya guiding principle. You ain't going to need it basically not putting in code, which you you don't actually need to do anything, and that is what a until face, which is too fat forces people to do it forces them to make implementations off members they don't need. And then the question is, What? What do you put in those members? I mean, do you throw an exception? Do you make and no up do you write to the log that this is being invoked even though it's not implemented and and so on and so forth. So instead off having all of this what you should do if the interfaces handling too many concerns at the same time is once again you split it into separate interfaces. In this case, it's also close to this idea of separation of concerns in that if your if whoever implements the interfaces has toe handle different concerns in different places, don't try to bundle those concerns into a single massive interface that everybody has to implement and has to pay the price for implementing effectively. So the lost principle that we looked at is the dependency inversion principle, and here the idea is simple is three idea that high level modules should not depend upon low level modules and that you should use abstractions where possible. So this is a very simple idea off object oriented design that if you have implementation details, then you shouldn't expose those implementation details directly. If you are emulating a database store, you should not expose the store itself because, well, this is what object oriented design is all about. It's the ability to change the internals off the system without changing the exposing interface. So instead of exposing, for example, a collection, what you can do is you can make a new interface and provide query mechanics for querying into the collection. Suddenly, when we talk about typical interfaces like I innumerable or I queria ble, for example, this is what it's all about. Instead of exposing the details off each different collection and their mechanic, you make a kind of general interface. And then the high level module can take this interface, perhaps as a constructive parameter using dependency injection or something to that effect . And it can start using this interface without knowing anything about the low level implementation details. And these are the solid design principles