Object-Oriented Programming | Solid Principles with Examples

Object-Oriented Programming | Basic concepts of Oops | Oops concepts | Object-Oriented Programming | Solid principles interview questions | Solid principles of Oops | Solid principles javascript | Solid principles in Design Patterns | javascript oops | Liskov substitution principle example | Dependency inversion principle example | Interface segregation principle example | single responsibility principle example | Open-Closed Principle
✔️ Let’s First Understand What is Good Code?
GOOD CODE QUALITY –
1. Readable
2. Testable
3. Extensible
4. Maintainable
5. Scalable (Data Structures & Algorithms & Infrastructure)
SOLID Principles – Designed by Robert C. Martin – Uncle Bob (nick name)
Single Responsibility Principle
Open/Close Principle
Liskov’s Substitution Principle
Interface Segregation
Dependency Inversion
💻 Let’s create a General Class structure –
A class contains attributes and behaviors.
class AnimalGeneral {
// [attributes] properties
weight: number;
species: string;
color: string;
numerOfLegs: number;
hasWings: boolean;
// [Behavior] Methods
eat() {}
fly() {}
swim() {}
hunt() {}
run() {}
}
NOTE: In this article, we will understand SOLID principles by examples of animals and their species and their different behaviors.
So SOME SPECIES EXAMPLES ARE –
mammals include rats, cats, dogs, deer, monkeys, apes, bats, whales, dolphins, and humans.
fish – Tuna, salmon, goldfish, and whales are mammals, not fish.
birds – Parrot, Crow, Kite, Eagle, cock
amphibians – frog, wood frog, toad
reptiles – turtle, lizard, crocodiles, snakes
▶️ Now Implement one method in the Animal class.
class Animal {
species: string;
// other properties...
swim() {
if (this.species === 'fish' || this.species === 'reptiles' || this.species === 'amphibians') {
console.log(this.species + ' can swim');
} else if (this.species === 'mammals') {
console.log('I cannot swim, I am not like a fish!');
} else {
console.log('Sorry I cannot swim');
}
}
}
▶️ Now Try to write Pseudo Testcases Code for the above Animal Class –
class AnimalTestCases {
animalObj = new Animal();
testFishSwim() {
this.animalObj.species = 'fish';
this.animalObj.swim();
}
testReptilesSwim() {
this.animalObj.species = 'reptiles';
this.animalObj.swim();
}
}
✔️ LET’S MEASURE THE ABOVE CODE QUALITY –
If there are 100 different species, we will need to implement 100 if-else cases. So what!? We have many if-else. Why is that a problem?
❓ Readable Yes. I can read & understand it. But actually no! As the number of species grows, the complexity is growing as well – becomes harder to read and understand.
❓ Testable Seems like it. I can write a test case for each species. However, the test cases and the code is coupled – changing the code for one species will affect the behavior of another.
For example – fish can swim fast than reptiles so if we console.log like ‘fish can swim fast’. This change for just only one species can break other test cases.
❓ Extensible Yes. If a new species get added tomorrow – all I have to do is add a new if-else condition. Will cover more on this later on the same blog. Continue reading…
❓ Maintainable If there are multiple devs working on different species, will there be any issues? Common issue – Merge Conflicts.
⭐ Single Responsibility Principle ⭐
- If some unit of code is serving multiple responsibilities – split it up into multiple units of code.
- Every function/class/module should have a simple, well-defined responsibility.
- Every unit of code should have exactly 1 reason to change.
class AnimalBase { species: string; // other properties... // This is common method for every animal. eat() {} } class Bird extends AnimalBase { swim() { console.log('I am from Bird group, Cannot swim!'); } } class Fish extends AnimalBase { swim() { console.log('I can swim fast. wooh...'); } } class Mammals extends AnimalBase { swim() { console.log('I am not like Fish. Sorry!'); } }
Now review the above code –
We created different classes based on species and extends them from the animal base class.
Every above-child class has its own swim method and is responsible for it only.
Also If later, you change a message of the Fish class swim method, other test cases will not break.
▶️ But There are problems with the above code –
Readable – We have a lot of classes/files now – readability is poor – But Let’s look at individual files I have a lot of units of code, but each unit is individually highly readable!
Testable – Better testability – because changing the behavior of Fish does NOT affect the behavior of others.
Extensible – Seems like no change.
Maintainable – At least the merge conflicts will be reduced.
Object-Oriented Programming | Solid Principles with Examples
🐦 Let’s Desing a Bird Library
// [ Library: Bird ]
class Birds extends AnimalBase {
// species inherited from the parent class AnimalBase
bird: string = '';
fly() {
if (this.bird === 'peacocks') {
console.log('I can cover short distances only by flying.');
} else if (this.bird === 'Penguins') {
console.log('I am a bird but cannot fly.');
} else if (this.bird === 'Crow') {
console.log('I can fly high.');
} else {
console.log('No idea..');
}
}
}
▶️ Ok, Now how do we use any Library in Client code?
We install packages of libraries that contain compiled code. Now by using the import statement, we import into our components and use its methods and attributes.
import Birds from 'BirdLibrary';
👀 Now Review what is the problem with the above Bird Library which we just created.
Not extendable – I want to “extend” this Bird functionality in my code to add a new bird but can not
do because the code is available in the compiled way.
👉 Let’s solve the above problem with the Open close principle.
⭐ Open/Close Principle ⭐
Your code should be closed for modification, however, it should still be open for the extension!
Now we need to understand why should Code not be available for modification.
▶️ Let’s How does the code development cycle work in big companies…
Step 1 – Developer writes code. And test it on your own machine.
Step 2 – Send to QA team for testing. – Manual testing, regression testing, integration testing, etc..
Step 3 – If all testing is well then the deployment cycle starts
Step 4 – Deployment Cycle –
1. Deployed code on a Staging server to test changes.
2. If all is well from 1 then deploy to the Prod server but only for a few users (5% users), not for
everyone.
3. Monitor changes, feedback, and errors for 5% of users. Test code for those.
4. Once all is well, then finally deployed for all users.
So it’s a very big process to deploy changes, If we modify in existing code. Other teams also use the same library, so modification can break other projects’ functionalities as well.
abstract class Birds1 extends AnimalBase {
// species inherited from the parent class AnimalBase
bird: string = '';
abstract fly(); // This is abstract method so cannot implement this here..
}
Concrete class – A class that extends the abstract class.
class Peacocks extends Birds1 {
fly(): void {
console.log('I can cover short distances only by flying.');
}
}
class Duck extends Birds1 {
fly(): void {
console.log('Generally don\'t fly. I found in places where there is water like ponds, rivers');
}
}
💡Now let’s check the above code –
Modification – Closed for modification.
Extension – Yes, Now we can add the fly method as per our need in the client code.
❓ Didn’t we make the exact same change for both Single Responsibility & Open Close?
Yes, we did! In both cases, we converted the if-else ladder to class inheritance.
❓ Is the SRP == Open/Close Principle?
No. The solution was the same, but the intention was different. The SOLID principles are inherently linked together.
✋ Analogy: if you speak the truth, you’re being honest. If you’re being honest, you’re obviously
speaking the truth.
▶️ Let’s see a problem with the above code –
😒 Kiwi or Penguin cannot fly but still needs to implement the fly method because that is abstract.
class Kiwi extends Birds1 {
// Concrete class
fly() {
// this is unused here..
}
}
👀 If we don’t implement the fly method, we will get compile error because of the abstract.
Abstract – Abstract class says that you can do something (like Bird can fly), ok, but How you can do that one, it needs to tell in child class.
💻 Let’s throw some exceptions from the fly method –
class Kiwi1 extends Birds1 {
// Concreate class
fly() {
throw new Error('Kiwi dont fly'); // throw exception.
}
}
💻 Let’s Create a Main class that uses the Bird class –
class Main {
main() {
var birdObj = new Birds();
birdObj.bird = 'peacocks'; // it is User choice.
birdObj.fly();
birdObj.bird = 'kiwi';
birdObj.fly();
}
}
// Run class
const mainObj = new Main();
mainObj.main();
// [LOG]: "I can cover short distances only by flying."
// [LOG]: "No idea.."
Before the extension, the above code is working fine as we have all If Else in one place. But after extension, Kiwi throws an error so the code will get an error. It could be the case that throwing an exception could break someone’s code.
Object-Oriented Programming | Solid Principles with Examples
▶️ Let’s solve the above problem of exception and error –
⭐ Liskov Substitution Principle ⭐
1. Any functionality in the parent class must also work for the child class.
2. If some piece of code works with a parent class P, then it should continue working without modifications, with any child class C extending P.
3. Any extension to existing code should not break existing code / violate expectations.
🎨 How should we re-design it?
abstract class Bird2 extends AnimalBase {
// species inherited from AnimalBase
bird: string = '';
// every bird can speak or eat so put these method here..
speak() {}
eat() {}
// don'\t define abstract method here like fly or swim etc...
}
💡 Create an Interface for Fly
interface IFly {
fly(): void;
}
Now We know Eagle can fly so implement the IFly interface. & As the Dodo bird cannot fly so no need to implement the IFly interface here.
class Eagle extends Bird2 implements IFly {
fly() {
// fly
}
}
class Dudo extends Bird2 {}
💡 Let’s create another interface ICanFly
interface ICanFly {
flapWings(): void;
kickOffGroundToTakeOff(): void;
fly(): void;
}
class Shaktiman implements ICanFly {
flapWings() {} // Shaktiman dont have wings so this method is unused here
fly() {
console.log('With one hand up, Shaktimaan spines & then fly away');
}
kickOffGroundToTakeOff() {}
}
class Airplane implements ICanFly {
flapWings(): void {} // unused method implementation because Airplane does not flap wings
fly(): void {
console.log('I can fly');
}
kickOffGroundToTakeOff(): void {
console.log('ready for take off..');
}
}
* PROBLEM IN THE ABOVE CODE –
Classes implement interfaces that’s why It is required to implement all its methods whether the class needs it or not.
⭐ Interface Segregation Principle ⭐
1. Keep your interfaces minimal.
2. No code should be forced to implement a method that it does not need.
✔️ To fix the previous code, we can split the ICanFly interface into multiple interfaces ICanFly, IHasWings, and IHasLegs.
- Implementation of Queue using Linked List | JavaScript
- Insert node in Linked list | Algorithm | JavaScript
- Insertion Sort in data structure | Algorithm with Examples
- Selection Sort Algorithm & K’th Largest Element in Array
- Quick Sort Algorithm with example | Step-by-Step Guide
- Dependency Inversion Principle with Example | Solid Principles
- Object-Oriented Programming | Solid Principles with Examples
- ASCII Code of Characters | String Operations with ASCII Code
- Negative Binary Numbers & 2’s Complement | Easy explanation
- Factors of a Number | JavaScript Program | Optimized Way
- LeetCode – Game of Life Problem | Solution with JavaScript
- Fibonacci series using Recursion | While loop | ES6 Generator
- JavaScript Coding Interview Question & Answers
- LeetCode – Coin Change Problem | Dynamic Programming | JavaScript
- HackerRank Dictionaries and Maps Problem | Solution with JavaScript
- React Redux Unit Testing of Actions, Reducers, Middleware & Store
- Micro frontends with Module Federation in React Application
- React Interview Question & Answers – Mastering React
- Top React Interview Question & Answer | React Routing
- React Interview Questions and Answers | React hooks
- Higher Order Component with Functional Component | React JS
- Top React Interview Questions and Answers | Must Know
- Interview Question React | Basics for Freshers and Seniors
- Cyber Security Fundamental Questions & Answers You must know
- Application & Web Security Interview Questions & Answers
- Top Scrum Master and Agile Question & Answers 2022
- Trapping Rain Water Leetcode Problem Solution
- Array Representation of Binary Tree | Full Tree & Complete Binary Tree
- Graphs in Data Structure, Types & Traversal with BFS and DFS, Algorithms
- Traversing 2 D array with BFS & DFS Algorithm in JavaScript
- Time Complexity & Calculations | All You should know
- Backspace String Compare Leetcode Problem & Solution
- Angular Interview Questions & Answers 2021 – Part 3 – JS Mount
- Why Angular is a Preferred Choice for Developers? Top Features
- Angular Interview Questions & Answers You should know Part 2
- Top 30 JavaScript Interview Questions and Answers for 2021
- React JS Stripe Payment Gateway Integration with Node | Step by Step Guide
- Create Year Month & Date dropdown List using JavaScript & JQuery
- Create Custom QR Code Component using QR Code Styling in React JS
- How to create a common Helper class or util file in React JS
- React Build Routing with Fixed Header and Navigation | React Router Tutorial
- React Create Dashboard Layout with Side Menu, Header & Content Area
- Web Application Security Best Practices | Top Tips to Secure Angular App
- HTML Form with Pure CSS & JavaScript | Custom Radio and Checkbox
- NgRx Top Interview Questions and Answers You should know
- Top 40 Awesome CSS Interview Questions & Answers You should know | CSS Tutorial
- Tips to Boost Angular App Performance | Web page Speed Optimization
- JavaScript Rotate 2D matrix 90 degrees clockwise | Top Interview Question
- HashTable Data Structure in JavaScript with Add Delete & Search Algorithms
- Trie Data Structure – Insert Search Delete & Print Program with JavaScript
Object-Oriented Programming | Basic concepts of Oops | Oops concepts | Object-Oriented Programming | Solid principles interview questions | Solid principles of Oops | Solid principles javascript | Solid principles in Design Patterns | javascript oops | Liskov substitution principle example | Dependency inversion principle example | Interface segregation principle example | single responsibility principle example | Open-Closed Principle