Solid Principles in a Flutter
These principles are commonly used by software engineers and provide developers with several benefits.
Hello Everyone, today we will be discussing a bit more general topic related to software engineering. We will look at patterns and also architectural decisions. The first part where we want to start is with the SOLID PRINCIPLES.
Almost every interview asks at least once about SOLID PRINCIPLES, one of the most common Software Development patterns. Thus, everyone should have a clear understanding of SOLID in Software Engineering.
SOLID stands for Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion, which we will discuss in the following sections. Developers generally recognize them as good practices and are very popular.
Each of them will be discussed and we will try to understand why they are important and why you should consider them in an object-oriented language like DART.
As we start, solid contains five principles, and we’ll take a look at each one. For each letter in solid, there is an abbreviation, and we’ll look at what they stand for. Hence, we will take a look at them all, but let’s take a moment to understand why we need solid in the first place. With solid, you can avoid bad software design patterns, engineering mistakes, and code depths, so your code will be easier and more readable as a result.
The SOLID principles were introduced in 2000 by software engineer and instructor Robert C. Martin, known as Uncle Bob. It is one of the most important standards in software engineering that nearly every developer who works with an object-oriented programming language has read at least once. If you want to learn more about the whole topic, I highly recommend reading that book. I have been asked at least once about solid in nearly every interview or coding interview that I have had, so I hope you will find this article useful and helpful.
S: The Single Responsibility Principle (SRP)
A solid principle begins with an “S”: Single Responsibility. This stands for the idea that every class in your code should have only one job or responsibility — it should do one thing and do it well.
In other words, rather than creating a single class that attempts to handle multiple tasks, it’s better to break down the tasks into separate classes, each responsible for a specific job. This approach makes your code easier to understand, maintain, and modify because each class has a clear and focused purpose.
Let’s use the analogy of robots with different roles to explain the concept of the Single Responsibility Principle:
Imagine you have a team of robots, each with a specific role:
- Chef Robot: This robot specializes in cooking. It knows all the recipes, handles the ingredients, and prepares delicious meals.
- Gardener Robot: This robot’s expertise lies in gardening. It takes care of plants, waters them, and ensures the garden flourishes.
- Painter Robot: This robot is an artist, focusing solely on painting. It selects colors, applies strokes, and creates beautiful artwork.
- Driver Robot: The driver robot excels in navigation and transportation. It knows the routes, drives efficiently, and follows traffic rules.
Now, let’s consider two scenarios:
Scenario 1 (Incorrect): One robot tries to handle all tasks — cooking, gardening, painting, and driving. This would be like a single robot attempting to be a chef, gardener, painter, and driver all at once. It would likely be overwhelmed, and the tasks would not be performed efficiently or effectively.
Scenario 2 (Correct): Each robot focuses on its specific role. The chef robot cooks, the gardener robot takes care of the garden, the painter robot creates art, and the driver robot handles transportation. This division of labor ensures that each robot excels in its designated task, leading to better performance and overall efficiency.
In the context of programming and the Single Responsibility Principle, the analogy illustrates that just as each robot should have a specific role, each class in your code should have a clear and focused responsibility. This makes the code more maintainable, understandable, and adaptable, just like the specialized robots working together efficiently.
let’s see an one of code example:
Bad Practice
Good Practice
O: The Open-Closed Principle (OCP)
In addition, there is a second big principle that starts with an “O” called the OPEN-CLOSED PRINCIPLE. This principle says that every class and method should be open for extension, but closed for modification.
In other words, you should be able to add new functionality to a class without altering its existing code. This principle promotes code stability and reduces the risk of introducing bugs when making changes.
Now, let’s relate this principle to your robot analogy:
- First Image (Incorrect Implementation):
Imagine a robot designed for specific tasks, such as cutting and painting. In the first image, the robot is initially capable of cutting. However, when there’s a need to add the ability to paint, the original design is modified. This modification might introduce errors or unintended consequences, potentially affecting the robot’s cutting functionality.
In summary, modifying the existing code to add new functionality violates the Open/Closed Principle.
- Second Image (Correct Implementation):
In the second image, the robot is designed with a more modular and extensible approach. Initially, it can cut. When there’s a requirement to add painting functionality, the robot is extended or enhanced without altering its existing cutting code. This means the robot remains closed for modification (its cutting code is not changed) but is open for extension (painting functionality is added).
In this way, the Open/Closed Principle is followed, allowing new features to be added without risking the stability of the existing code. This approach promotes a more maintainable and scalable system.
Look at this by code example:
Bad Practice
Good Practice
L: The Liskov Substitution Principle (LSP)
The third big principle that starts with an “L” is LISKOV SUBSTITUTION PRINCIPLE this is quite a mouthful and is also one of the hardest parts of software engineering to grasp because the principle is difficult to explain, so we’ll try to explain it a bit easier so that the list of substitution principle can be explained more easily. As I mentioned, the purpose of substitution is to replace a subtype somehow with its general parent type, so let’s say we take a robot as an example.
Imagine we have two robots: Sam and Eden. Sam proudly declares, “Hi, I am Sam, and I can make coffee.” Now, Eden introduces itself, saying, “Hi, I am Eden, Sam’s child.”
In the first case, someone asks Sam, “Hey Sam, can you make me coffee?” Sam confidently replies, “Yes, I can make coffee for you.” This establishes Sam as a capable coffee-making robot.
Now, consider the incorrect scenario. Sam is not available, and someone asks Eden, “Hey Eden, since Sam is not here, can you make me coffee?” Unfortunately, Eden responds, “I can’t make coffee for you.”
This violates the Liskov Substitution Principle because, as Sam’s child, Eden should inherit the ability to make coffee. The principle suggests that objects of the subclass (Eden) should be able to replace objects of the superclass (Sam) without altering the program’s behavior. In this case, Eden should seamlessly substitute for Sam and be able to make coffee.
In the correct scenario, when asked the same question, “Hey Eden, since Sam is not here, can you make me coffee?” Eden responds confidently, “Yes, I can make coffee for you.” This adheres to the Liskov Substitution Principle, ensuring that the subclass (Eden) maintains the expected behavior of the superclass (Sam) when performing tasks like making coffee.
According to the LSP(LISKOV SUBSTITUTION PRINCIPLE), subclasses should be replaced with superclasses without changing the logical correctness of the program. Essentially, a subtype must guarantee the “usage conditions” of its super-type along with some additional behaviors.
For example:
Bad Practice
Good Practice
I: The Interface Segregation Principle (ISP)
So this brings us to our fourth principle, the fourth big principle that starts with an “I” is INTERFACE SEGREGATION PRINCIPLE which sounds a bit complicated, but is actually quite easy to comprehend. We do not want to force our clients to use an interface that contains functions or methods that they don’t use easily enough.
Let’s simplify this with robot analogy:
The image illustrates the Interface Segregation Principle (ISP), which is another important concept in software design. To explain this principle clearly, let’s use an analogy involving a group of robots and their exercises.
On the left side of the image, we see two robots looking at a list of exercises that all robots are supposed to do. The list includes tasks like spinning around, rotating arms, and wiggling antennas. However, one of the robots comments, “Oops! But I don’t have antennas.” This situation highlights a violation of the Interface Segregation Principle because the robot is forced to implement an exercise (wiggling antennas) that it cannot perform. This can lead to confusion and unnecessary complexity, as not all robots need to perform every action on the list.
On the right side, the scenario shows a better approach where the exercise list is tailored to each type of robot. The new exercise lists are specific: one for robots that can spin around, one for robots that can rotate their arms, and one for robots that can wiggle their antennas. The robot on the right responds with “Awesome!” This represents adherence to the Interface Segregation Principle, as each robot only receives the instructions relevant to its capabilities. This design keeps the interfaces clean and focused, ensuring that robots only implement what they actually need.
In summary, the Interface Segregation Principle can be understood in analogy as a situation where robots are given specific exercise routines that match their abilities, rather than forcing them to adhere to a general list that includes irrelevant tasks. By focusing on specialized interfaces, the system becomes easier to manage and understand, reducing complexity and improving overall functionality.
According to this principle, clients don’t need to implement behaviors they don’t want. As a general rule, you should create small interfaces with few methods.
Let’s look at this example:
Bad Practice
Good Practice
In this corrected implementation, each robot class implements only the interface relevant to its functionality, adhering to the Interface Segregation Principle. The PainterRobot
implements the PaintingRobot
interface, and the CleanerRobot
implements the CleaningRobot
interface. This design ensures that each class is focused on its specific set of tasks, promoting modularity and maintainability.
In analogy, imagine you have two workers, a Painter and a Cleaner, each with their set of tasks. The Painter is skilled in creating artwork, and the Cleaner specializes in keeping surfaces clean. If you were to give them a shared to-do list that includes both painting and cleaning tasks, it would be impractical and inefficient. Instead, providing each worker with a list tailored to their expertise ensures that they can efficiently perform their designated roles without unnecessary obligations. This aligns with the Interface Segregation Principle, promoting focused and specific interfaces for individual classes.
D: The Dependency Inversion Principle (DIP)
Lastly, the last principle that is very important in software engineering, which starts with a “D”, is the DEPENDENCY INVERSION PRINCIPLE, which states that high-level modules must not depend on low-level modules without an abstraction. That’s a lot, but as an example, it’s quite easy to understand.
The image illustrates the Dependency Inversion Principle (DIP), which is an important rule in software design. To explain this principle clearly, let’s use an analogy of a chef and their kitchen tools.
On the left side of the image, we see a robot that is like a chef who can only use a specific tool, such as a pizza cutter arm. This chef is stuck because they can only cut pizza with that one tool. If the pizza cutter breaks or if the chef wants to try a different method, they can’t adapt easily. This situation reflects poor design because the high-level task (cutting pizza) depends too much on a specific low-level tool (the pizza cutter arm). This makes the kitchen inflexible, just like software that is hard to change.
On the right side of the image, the robot represents a chef who is flexible and can use any kitchen tool available, like a knife, scissors, or even a spatula. This chef can adapt to different tasks easily because they are not limited to just one tool. Instead, they depend on general cooking skills (the abstraction) rather than being tied to a specific tool (the implementation). This is a much better design because it allows for easier updates and changes in the kitchen.
In summary, the Dependency Inversion Principle is like a chef who can use any kitchen tool instead of being stuck with just one. By focusing on general cooking skills rather than specific tools, the chef can create a more efficient and versatile kitchen. Similarly, in software design, relying on abstractions rather than specific details helps create systems that are easier to maintain and adapt over time.
According to the DIP(DEPENDENCY INVERSION PRINCIPLE), abstractions should be preferred over implementations. It’s good to extend abstract classes or implement interfaces, but it’s bad to descend from concrete classes. It’s already clear why when we look at the open-closed principle. Take a look at this example:
Bad Practice
Good Practice
So we talked about solid the five principles from Robert c martin or how we call them all the uncle bob so let’s recap five principles in solid “S” for single responsibility “O” for open close principle “L” for a list of substitution principle “I” for interface segregation principle and “D” for dependency inversion principle alright.
Now you are prepared for your interviews. Furthermore, SOLID principles were developed to combat these problematic design patterns. SOLID principles aim to reduce dependencies so that engineers can change one area of software without affecting others. As well as making designs easier to understand, maintain, and extend, they were also designed to make them simpler to maintain and extend.
So that’s all the information of Solid Principle, that I want to describe in short. I took it from many websites as some research also images that i collected by many places, if you find out any wrong info or misdirected, please point out or comment below.
If you got something wrong? Mention it in the comments. I would love to improve. your support means a lot to me! If you enjoy the content, I’d be grateful if you could consider subscribing to my YouTube channel as well.
I am Shirsh Shukla, a creative Developer, and a Technology lover. You can find me on LinkedIn or maybe follow me on Twitter or just walk over my portfolio for more details. And of course, you can follow me on GitHub as well.
Have a nice day!🙂