Design Patterns Part 1: Solid Principles
Sometimes It’s not enough to just know how to write a code that works for your customers. The world is a place where each day something changes, doesn’t matter what, but something changes and what we have to be ready for is to adjust to the change. Adjusting the change the best way is what design patterns can give you for your software development skills. Writing a good, solid code is just an art which is something you can’t just get in a week or year.
So, what are the design patterns? a long time ago, (I won’t try to give you history lessons), programmers made so many mistakes when writing softwares and what it caused them was that it was so hard for them to add new features to their softwares. By making mistakes, we learn right? So people just decided at that time to sit down for a while and give it a thought about doing something that wouldn’t cause the same thing again. They invented techniques and ways of how to write a flexible code so that we don’t have to go through their mistakes and do our code-software the better way. Design patterns are not keywords, code lines or something like that. They’re just ways, solutions to the problems you encounter in the middle of writing your software.
Let’s Begin With the first one: Solid Principles.
Solid Principles consists of 5 principles that can help you to learn techniques so you’ll try your best to write a better, testable code and adjustable for the changes of course.
Principle 1) Single Responsibility Principle
SRP indicates that a class should have one and only one responsibility. Before showing you a good example, I first prefer to try to make you understand what it states. Imagine in your software, You have a class, named ‘User’. In this class, you have functions like = “Insert()”, “Update()”, “Delete()”, “makeTheInvoice()”. Let’s think about the problem we’re facing for this class and especially for this function. This class doesn’t seem to have a single responsibility, meaning that It has 2 responsibilities. Why is that? “Insert(), Update(), Delete()” are the same responsibilities as It’s about user behavior and making database for the users, but if we look at another function “makeTheInvoice()”, you might presume that It’s idea is something else and not is close to user’s responsibilities. So you know what responsibilities mean, then we can argue why it’s a bad practice for a class to have more than one responsibility.
You remember, What I said? The best code is the best when It’s adjustable to the changes and you don’t have to cry a lot to make the new features. If your code is bad, you’re gonna have to do enermous energy to make the change and imagine Big companies like “facebook”, “netflix”. If they write such a bad code, when there’s a new feature, how hard it could be for such many people to make the change and divide the work? To sum up, when the code is of a bad quality, It’s super hard to make the change, add a new feature or divide the tasks between your employees.
So When There’s a SRP(Single Responsibility Principle) , There’s a better probability that you won’t have to do much to make the change. If we have 4 functions for the user class “Insert, Update, Delete, MaketheInvoice()” , then you’d have to go there to make a change for one responsibility “Insert,Update,Delete” and if there’s a necessary change for the invoice, you still go to that class.
Disadvantages for going to the same class for two responsibilities:
i) If the class(User) is too big and so complex code in there and It’s there on a server, and you want to just change a one line of invoice code, you still need to change the User class and redeploy again on a server. What if That class has been already tested, and some bad developers changed that one line of invoice function code, and they also (by mistake) made something bad in other functions, then what? It doesn’t seem good. It’d be much better to have an invoice class and user class separated and changing one doesn’t mean to touch the other class. That’s the beauty of it.
SO have a single responsibility for each class and everything’ll be much easier when you reach a big, huge, complex structures in your huge application. For a small application, when you know, there’s nothing you can worry about after finishing the project, then you don’t need to use this or any other design patterns.
I’ll post a link of good examples of these principles after finishing all of them.
Principle 2) Open-Closed Principle
What this principle says is that “It’s open for extension, closed for modification”. Let’s talk about what this means. Sometimes, when you write a huge amount of code, there’re core(important files) that we never want to change. Let’s say Database Class. If we write it the bad way and after a month, our CEO tells us that we need to add new feature, It’s really difficult to go to that class and change it, because changing it might cause many abominable issues. It’s not about only changing it. Imagine that to add new feature, 10 people have to work with this specific class. If 10 of them go and try to change the code of that class and add new features by changing things, it’ll soon become really tedious and hard to maintain. Wouldn’t it be better if we could write a code so that there’s no necessity to ever change it and add new features to it the other way which doesn’t include changing any lines of code in that file. That’s what those words mean = open for extension, closed for modification. To follow this principle, this principle advises us to use interfaces. As I said, After finishing the explanations, I’ll provide the link that better describes this principle by showing you examples.
Principle 3) Liskov Substitution Principle
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Let’s break down these words to better understand what they mean. Sometimes what really happens in huge applications is that we pass different kind of objects to a function. By different kind of objects, I mean a parent class and subclasses. Let’s say a parent class is called : “Animal” and there’re subclasses which extends this “Animal” Class : “Dog”, “Lion”, “Tiger”. If we pass the “Animal” Type object to the function, It works perfectly, But there’ll be scenarios when we want to pass the child of this “Animal” class which might be “Dog” or “Lion” or “Tiger”. The words above say that if the program works well when passing a parent object to a function, it should also work while passing a child object to a function without breaking the code.
An example link will be provided later.
Principle 4) Interface Segregation Principle
No client should be forced to depend on methods it does not use.
Many client-specific interfaces are better than one general-purpose interface.
Sometimes, we have an interface. Let’s call it : OrderProcessor. There’re two classes that implement this interface. So whatever virtual(abstract) functions are in an interface, they must be overridden(all of them) by the classes that implement this interface. So whatever virtual functions were written in OrderProcessor interface, we also wrote their implementation code in two other classes. Let’s say that one of the classes doesn’t need all the functions that is in an interface. It doesn’t need those functions, but we still have to write their implementations.(No other way). So here is the problem. It turns out one of the classes depend on methods that it doesn’t use. Why is that a problem? It’s a problem because if in an interface we change the header of any function or delete the function, we have to delete or change them in classes that don’t even use this function, but we were obliged to write their implementation. If there’re 100 classes that implement this interface and 50 of them implement this interface because they need only one function from this interface and not the other ones, and if we decide to delete one function from interface, we don’t have to only delete this implementation code in 50 classes that really use them, we just have to go to 50 other classes and delete that function also. You see how much work needs to be done? I think you’ll much better understand this principle by looking at the examples. I just wanted to give you an idea and how this problem can arise.
Principle 5) Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
This is the principle that no one can really give you an deep understanding. You just have to master it. All I can tell you is when we really need to follow it.
This problem arises when people write a class and that class for example is an Invoice class. After generating an invoice, they create an Email class object by specifying “new” keyword. Imagine at a later time, we don’t want to send an email only, but a sms also. What do you do? you go that class and you also write “else if” and in that code, you create a new object of Sms class. That’s the problem, because it’s not open for extension. You’ll understand that by looking at an example.
So we talked about the SOLID principles that are very useful and help you write a better maintanable code. A very useful examples are shown on this website. Just take a look and you’ll see that by reading my assumptions and logic, how awesome these principles are. Just remember that you don’t use any of it if you just write a little application that doesn’t need anything but a blog. You should know when you need these principles, because if you don’t understand when to use them and you still use them, there’s a high probability that by using them, you write a worse code.