Let's see, should we rename the single responsibility principle? If we had to rename it, what name would we choose? Well, that's something I've given some thought to, and I am reluctant to rename any principle because they're fairly well entrenched into our community at this point. But if we had to change the name of it, the single responsibility principle, Maybe I would call it the single source of change principle, but even that just doesn't quite do it. So, you know, actually the name is not my name. It was named by Bertrand Meyer a long, long time ago. So probably not going to change it. Let's see. There's a question here. What do you think about big classes? Well, I mean, generally speaking, things in software should be small. Functions should be small. Classes should be small. Files should be small. We don't want great big sprawling things in software. And the reason we don't want great big sprawling things in software is that we like names and little things have to be named. Big sprawling things, that's a lot of junk in there that doesn't have a name. We like to name things. We like to isolate them. We like to encapsulate them. We like to protect them from dependencies. And so we like everything to be small. And I'm not going to talk about this today, but if you were to take my clean code course, you would find that I like things smaller than you might want to make them. And I think things ought to be really small. Let's see. Can we say that always when we have a new inside of a class, it is a bad smell? No, you can't say that. And that's something we'll get to when we talk about the dependency inversion principle. There are certain classes that are concrete that you should not worry about using a new with. On the other hand, there are other classes that it's better if you can find some abstract way to create. And when we get to the dependency inversion principle, if we get that far today, we will talk about the abstract factory pattern and the way you can use that to help. Let's see, do we have an example of a module that cannot be closed for modification? Well, it's not that it cannot be closed. It's that we don't want it to be open for modification. Want it to be closed. We try and get it to be closed. And the kinds of modules that we close for modification are the modules that contain all of our high-level policies. High-level business rules, high-level policies, those we want closed for modification, but open for extension. At the very top level of the system, there are the top-level business rules. They're probably never going to change. Once you've got the top most level business rules, they're very unlikely to ever need change. So we'd like them to be closed for modification. At the very bottom of the software hierarchy, you've got all the little fiddly little details, and they're going to change all the time, right? So we want to be able to make changes there. And then as you go through the levels from slightly higher level to even higher level to very high level, all the way up to the top level business roles, there are varying degrees of this openness and closeness, right? So at the topmost level, we want to be closed for modification. At the next level down, we want to be mostly closed for modification, but we might make a modification there if it protects the highest level from modification. And that's the way you think about it going down. It's this spectrum or this continuum of change, this open-closed thing that is very closed at the top and very open at the bottom and different degrees in the middle. All right, the Liskov substitution principle. Let us go right there. This is Barbara Liskov. And Barbara Liskov Substitution Principle. Let us go right there. This is Barbara Liskov. And Barbara Liskov was a mathematician, is a mathematician. She's not dead. And she was a mathematician in computer science and created a definition for a subtype. There's a whole bunch of reasons why she created this definition. I'm not going to go into why, but let me tell you what the gist of it was. The gist of it was this. If you have a type, and in this case, the type is a rectangle, and if you have a user of that type, which in this case is the word user or the class user, then a subtype of rectangle is a type that can be given to the user without the user knowing that it's not a rectangle. In other words, it can be substituted for the rectangle. It can be substituted for the rectangle. It can be substituted for the type. So a subtype is a type that can be substituted for the base type in the context of a user. You've got to have all three of them. The definition of subtype requires that there's a's a user a base type and a subtype now the reason i put rectangle and square up here is because this actually is a violation of the liskov substitution principle for a very interesting reason first of all i want you to think of what's inside a rectangle this is a class that represents a rectangle and what's inside it is the height and the width of the rectangle, right? Rectangle has two different dimensions, height and width. And of course it probably has methods for setting the height and width. So that's what those set H and set W things are, set height and set width. And then the designers of this particular program said, oh, well, we need a square too. And of course a square is a rectangle. And notice the turn of phrase there. A square is a rectangle. In most object-oriented books and classes that you might take, the words is a imply inheritance. So square is a rectangle means that square inherits from rectangle. However, that doesn't work very well because the square does not need two dimensions. It only needs one dimension. And the name of that dimension is not height or width. It's a side. And yet name of that dimension is not height or width, it's side. And yet it's going to inherit those functions. The square is going to inherit the fields, the height and width fields, and it's going to inherit the set height and set width functions. And that's just wrong. It doesn't work right. Now, you can force it into working by overriding the set height function to also change the width. So in square, you override set height to also change the width, and you override set width to also change the height. And that will make the square behave like a square. It will be a square, right? And then there's nothing you can do to it to make it not a square. However, this causes a really big problem. And that problem is back in the user, because the user expects to be using a rectangle. And let us say that the user calls setHeight. Does the user have the right to expect that when they call setHeight, the width does not change? And yes, the user has that right because the user is using a rectangle. And when you change the height of a rectangle, the width does not change. But if we pass the user a square, then the user will ignorantly call set height, and the width will change. And this will corrupt the user's internal logic, and the user will throw a null pointer exception a billion instructions later after corrupting both the stack and the heap. And you, the programmer of the user, will get a logic analyzer out, and you will backtrace the stack for a billion instructions until you finally realize that the reason you threw a null pointer exception is because somebody passed you a square instead of a rectangle. And you will then solve that problem by putting an if statement into the user. And that if statement will be, if the rectangle is a square. And that if statement will violate the open-closed principle because now you've had to modify the user because of a detail. You've had to modify a higher level policy because of a lower level detail. So every violation of the substitution principle becomes a violation of the open closed principle. Now, what went wrong here? Isn't a square a rectangle? And the answer to that is yes, a square is a rectangle, but that's not a square down there. That is a class that represents a square, but that's not a square down there. That is a class that represents a square, but it is not a square. And the class that represents a rectangle is not a rectangle. And the fact that a square is a rectangle does not mean that the square class is a rectangle class. And it's not, it doesn't behave the same way. This is called the representative rule. The representative rule says that the representatives of things do not share the relationships between the things they represent. And if that was too much of a word salad, let me explain it. Some of you may have gotten divorced at some point in your life. If that happened to you, I'm sorry. But I presume that you both had lawyers representing you. Those two lawyers were not themselves getting divorced. The representatives of things do not share the relationships of the things they represent. How do you know if you're violating this principle? Anytime you create a subclass that does less than the base class or does something that the users of the base class don't expect, then you have violated this principle, right? And you will know that you have violated this principle when sometime later you are forced to put if statements in that check the type of the object that has been passed to you. that check the type of the object that has been passed to you. Someone passed a square to the user, and the user had to put an if statement in to check to see that the rectangle they expected was really a square. And that is the Liskov substitution principle. Now, let us go to the interface segregation principle. The interface segregation principle. The Interface Segregation Principle. This one's going to take a little bit of discussion. And the name of this principle is also very bizarre. And it came about because of the way this principle came into being. I haven't changed the name of the principle. It wouldn't be a good idea. But let me explain how this principle works. And it all goes back to something that happened a very long time ago at a company called Xerox. But before I tell you that, here's the gist of the principle. Don't depend on things you don't need. Don't depend on things you don't need. Okay, now let me tell you the story of Xerox. Long, long ago, in the early 90s, I was a consultant at Xerox, helping them with C++ object-oriented design. They were in the midst of working on the very first digital color copier, on the very first digital color copier. And they decided that they were going to implement the entire thing in C++ using microprocessors. And this is back in the days when that was a big deal. These were 68,000 microprocessors, right? Motorola 68,000. Very nice little machines with the one microsecond cycle time and maybe 64K of memory. You know, back in the days when that was a big machine. And they implemented all of the old hardware functionality in software. So this is one of their first machines that was going to be entirely software driven. Now, the architecture of a printer or a copier in hardware looks like the diagram that I've just shown you. Right. So there's the feeder hardware and there's the inverter hardware. The inverter turns pages upside down so you can do double sided copy. And then there's the xerography component, which does the actual printing. Then there's the collator component and the stacker component and the stapler component and the trimmer component. There's a whole bunch of these. And this is all the hardware. The hardware is broken into those components. At Xerox, the software was broken up the same way. This was an artifact of the way Xerox managed their software team. They would assign software teams to serve hardware teams.x managed their software team. They would assign software teams to serve hardware teams. So the feeder software team would serve the feeder hardware team and so on and so on. Therefore, we had a feeder module, software module, an inverter software module, a xerography software module, and so on and so on and so on. And then there was the job class. The job class was the class, the software class, that described the entire print job. And it had things like the number of pages and all the images that would be printed and whether it should be inverted or stapled or stacked or trimmed or collated, all these little attributes were encoded into the job class. Which, of course, means that all of those lower level modules had to know about the job class. They all depended on the job class. They all depended on the job class. That black arrow there, that big black arrow that fans out and points a job, those were source code dependencies. The author of the job class came to me one day and said, man, I can't get anything done. It takes me forever to do a recompile. Because if I touch even one line in the job class, I've got to recompile the entire system. And that takes 45 minutes. That was in the days, you know, in the early 90s, when computers were slow enough that to do a relatively large C++ compile would take hours. So he's saying it's taking me 45 minutes to an hour just to recompile everything. I can't make any changes to the job without invoking this massive recompile. So we thought about that for a while, and we realized that the problem was that when he made a change to the job class, was that when he made a change to the job class, he might make a change to the job class that only really affected the feeder, but everything else had to be recompiled because they all depended on the job class. Is there a way to isolate the feeder so that the feeder only depended on the feeder-like things in the job class? And the answer to that is, well, yes, you could do this. Now, multiple inheritance was a brand new feature in C++ at the time. The compilers integrated this feature back in 1992. So real early. And we had just gotten a version of the compiler that had multiple inheritance. So there we were, all of a sudden, we could say this. The feeder will depend on the feeder job. That will be an interface, but it will only have the methods in it that the feeder wants. It's not going to have all the other methods, just the methods that the feeder wants. It's not going to have all the other methods, just the methods that the feeder wants. The inverter will depend on the inverter job, and the inverter job will declare only those methods that the inverter wants, and so on and so on and so on. We segregated the interface of the job out to the individual clients of the job. Each client had its own interface that only had the methods that that component needed, that that client needed. And then all of those interfaces were multiply inherited into the job class. This solved the problem. Now, if you made a change to the job class that only affected the feeder, then you would have to change the feeder job interface and the feeder, but nothing else would have to change and nothing else would have to be recompiled. This solved the problem. That's why this is called the interface segregation principle. But the reality of this principle goes far deeper than just that. The reality of this principle is that in general, when you depend on something, you want to make sure that that thing you are depending on does not contain things that you don't need. Don't depend on things you don't need. Because when you depend on things you don't need, when those things that you don't need change, you are going to be subject to change yourself. Depend on abstractions that protect you against change. And that is the interface segregation principle.