Understanding the Power of the “S” Stone: Single Responsibility Principle
Imagine Uncle Bob is a gardener responsible for cutting grass, washing cars, and doing laundry. That’s a lot of work, but Uncle Bob manages it all quite well. However, one day your dad tells Uncle Bob that the cars need to be washed in a new, special way to avoid rust. Now, Uncle Bob, being just a gardener, struggles to keep up with your dad’s new demands.
You take a moment to think and use the power of the "S" stone (the Single Responsibility Principle). Instead of asking Uncle Bob to handle everything, you suggest, "Why not have a car washer for the cars and a maid for the laundry?" This way, Uncle Bob can focus on gardening, and if the car washing method needs to change in the future, it’s easy to tell the car washer, without bothering Uncle Bob.
Your dad agrees with this brilliant idea, and now the responsibilities are properly divided. Uncle Bob sticks to gardening, while others handle washing cars and doing laundry.
Key Takeaway:
By applying the Single Responsibility Principle, we ensure that each task or role has one clear responsibility. This makes it easier to manage, adapt, and scale, without overloading anyone with too many tasks.
Before
class Gardener{
void doWork(){
// does a set of jobs
cutGrass();
waterPlants();
washCars();
doLaundary();
}
void waterPlants(){
// waters the plants
}
void cutGrass(){
// cuts the grass
}
void washCars(){
// washes cars
}
void doLaundary(){
// does laundary
}
}
After using “S” stone ::
class Gardener {
void waterPlants(){
// waters the plants
}
void cutGrass(){
// cuts the grass
}
}
class CarWasher {
void washCars() {
// washes cars
}
}
class LaundryWorker {
void doLaundry() {
// does laundry
}
}
Understanding the Power of the “O” Stone: The Open-Closed Principle
Imagine everything is going smoothly, and then your dad asks Uncle Bob to do some fancy grass cutting. You go to Uncle Bob and ask him to take on a new task, which means he has to learn something new. This slows down everything because now every gardener needs to learn this new skill, and it's becoming a mess.
To solve this, you use the power of the "O" stone (the Open-Closed Principle). Instead of making Uncle Bob constantly take on new tasks, you rethink the roles. Uncle Bob is a Gardener, so he can do basic gardening tasks. For the special grass cutting job, Uncle Richard is the right person. Uncle Richard is a Gardener, but he also specializes in cutting grass in a fancy way, so he’s a SpecialGardener.
Key Insight:
Uncle Bob is a Gardener who does basic gardening tasks.
Uncle Richard is both a Gardener and a SpecialGrassCutter, meaning he can do the basic gardening tasks plus the fancy grass cutting.
The Rule:
A Gardener should be able to cut grass.
A SpecialGardener should be able to cut grass in a fancy manner and still do everything a basic Gardener does.
By following this principle, you avoid disturbing Uncle Bob and make the system more efficient. Uncle Richard takes on the special tasks without impacting the general gardening work.
before
class Gardener {
void doWork() {
cutGrass();
waterPlants();
}
void waterPlants(){
// waters the plants
}
void cutGrass() {
// cuts the grass
}
void fancyGrass(){
// cuts grass in a fancy manner
}
}
after using “0” stone :
class Gardener{
void cutGrass(){
// cuts the grass
}
void waterPlants(){
// waters the plants
}
}
class SpecialGardern extends Gardener{
void cutsFancyGrass(){
// cuts fancy grass
}
}
void main() {
var uncleBob = Gardener();
var uncleRichard = SpecialGardern();
// uncle bob can cut grass
uncleBob.cutGrass();
// uncle richard can cut grass
uncleRichard.cutGrass();
// he can also cut grass in a fancy way
uncleRichard.cutsFancyGrass();
}
Understanding the Power of the “L” Stone: Liskov Substitution Principle
Everything was working well until your dad asked Uncle Richard to water the plants. But Uncle Richard replies, "I don’t water plants, I cut grass in a fancy way. That’s my job!" Your dad insists, "But you’re a gardener, so you should water plants too!"
At this point, you realize the mistake you made. You assumed Uncle Richard, being a gardener, should water plants. But the assumption was wrong. Uncle Richard isn’t just a gardener; he’s a grass cutter. A gardener can cut grass and water plants, but a grass cutter is only responsible for cutting grass.
So, after thinking it through, you revise your approach. You decide that a GrassCutter cuts grass. A Gardener, on the other hand, not only cuts grass but also waters plants. So, a Gardener is a type of GrassCutter, but a SpecialGrassCutter like Uncle Richard specializes in cutting grass in a fancy way.
Now, it all makes sense: Uncle Richard is a SpecialGrassCutter. He can cut grass normally or in a fancy way, but he isn’t responsible for watering plants.
Key Takeaway:
Remember, the principle says, "All children should be able to do what their parent does." In this case, Uncle Richard, as a GrassCutter, can cut grass, just like his parent class. He doesn’t need to water plants, as that’s the responsibility of a Gardener.
class GrassCutter{
void cutGrass(){
// cuts the grass
}
}
class Gardener extends GrassCutter{
void waterPlants(){
// water the plants
}
}
class SpecialGrassCutter extends GrassCutter{
void cutsFancyGrass(){
// cuts fancy grass
}
}
void main() {
var uncleBob = Gardener();
var uncleRichard = SpecialGrassCutter();
// uncle bob can cut grass
uncleBob.cutGrass();
// uncle richard can cut grass
uncleRichard.cutGrass();
// he can also cut grass in a fancy way
uncleRichard.cutsFancyGrass();
}
Understanding the Power of the “I” Stone: The Interface Segregation Principle (ISP)
Imagine Uncle Sam runs a tool shop. One day, he tells Uncle Tom, a plumber, to fix a faucet. But Uncle Tom says, "I don’t fix faucets, I install pipes!" Uncle Sam mistakenly assumed all plumbers should do both tasks. This is where the Interface Segregation Principle (ISP) helps.
The Problem: Too Many Responsibilities
At first, Uncle Sam had a Plumber class that included both pipe installation and faucet fixing. But Uncle Tom only installs pipes, so this design wasn’t right for him.
The Fix: Split the Tasks
Instead, we can break the tasks into separate interfaces:
// Plumber interface
abstract class Plumber {
void installPipes();
}
// Faucet Fixer interface
abstract class FaucetFixer {
void fixFaucet();
}
The Workers
Now, PipeInstaller does only pipes, and FaucetRepairPlumber does both pipes and faucet fixing:
class PipeInstaller implements Plumber {
@override
void installPipes() {
// Installing pipes
}
}
class FaucetRepairPlumber implements Plumber, FaucetFixer {
@override
void installPipes() {
// Installing pipes
}
@override
void fixFaucet() {
// Fixing faucet
}
}
Why ISP is Good
No Unnecessary Tasks: PipeInstaller doesn’t need to fix faucets.
Clearer Roles: Each worker does only what they need.
Easier to Add New Roles: Adding new jobs is simple without affecting others.
Final Thoughts
The I stone (ISP) helps by keeping tasks separate. It ensures each worker only does what they’re meant to do, making the design cleaner and easier to expand.
Understanding the Power of the "D" Stone: The Dependency Injection Principle
Now that we’ve figured out how to handle everything, what do we do next? You don’t want to manage it all, right? You’re thinking, "I’m not going to manage this." So, what do you do? You bring in a manager—someone whose job is to manage everything. Everything is running smoothly; you tell the manager, "Hey, tell the plumber to do this," or "Tell the gardener to do that."
However, remember that initially, you tell the manager to only communicate with a regular gardener to cut the grass. But what if you want the manager to tell a special gardener to do the work? Since the manager was only instructed to deal with the normal gardener, it won’t work with the special gardener.
Here’s the solution: rather than pre-defining which gardener the manager should tell, pass the gardener to the manager as a parameter. This way, you don’t have to change the manager’s instructions. You simply pass the special gardener to the manager, and the manager will treat it the same way.
But here's the important part: in order to make this work, the special gardener must have the same functions as the normal gardener. Otherwise, if the manager tells the special gardener to cut grass, and the special gardener doesn’t know how, it will cause chaos.
This concept is called Dependency Injection. Let’s see how we can apply it in code.
Before:
class CarWasher {
void doWork() {
washCars();
}
void washCars() {
// washes cars
}
}
class LaundryWorker {
void doWork() {
doLaundry();
}
void doLaundry() {
// does laundry
}
}
class Manager {
void manage() {
CarWasher().doWork();
LaundryWorker().doWork();
GrassCutter().cutGrass();
}
}
After:
class Manager {
var carWasher;
var laundryWorker;
var grassCutter;
Manager(this.carWasher, this.laundryWorker, this.grassCutter);
void manage() {
carWasher.doWork();
laundryWorker.doWork();
grassCutter.cutGrass();
}
}
void main() {
var manager = Manager(CarWasher(), LaundryWorker(), SpecialGrassCutter());
manager.manage();
}
Explanation:
Instead of hardcoding the responsibilities in the manager, we inject the dependencies (like CarWasher, LaundryWorker, and GrassCutter) through the Manager constructor.
This approach makes the Manager more flexible, as it can now manage any type of GrassCutter, including special ones, without needing to change the logic of the manager.
The Manager simply calls the doWork method, no matter which specific worker it is given, as long as the worker has the necessary functions.