SOLID Principles
SOLID Principles
=======================
Pragy
https://linktr.ee/agarwal.pragy
💎 Key Takeaways
================
❓ FAQ
======
▶️Will the recording be available?
To Scaler students only
🎧 Audio/Video issues
Disable Ad Blockers & VPN. Check your internet. Rejoin the session.
💡 Prerequisites?
Basics of Object Oriented Programming
Important Points
================
>
> ❓ What % of your work time is spend writing new code?
>
> • 10-15% • 15-40% • 40-80% • > 80%
>
<= 15% of their work time will be spent writing actual new code!
✅ Goals
========
Minimize my work time and maximize my play time (while ensuring high salary)
1. Readability
2. Maintainability
3. Extensibility
4. Testability
===================
💎 SOLID Principles
===================
- Single Responsibility
- Open Closed
- Liskov's Substitution
- Interface Segregation
- Dependency Inversion
💭 Context
==========
During a 3 hour class, can we write all the code for a large company like
Amazon?
Any real problem will have a lot of nuances - and coding it will take time.
It's always better to start from curated examples - to show the gist of
something
- Zoo Game 🦊
- Characters - staff, animals, visitors
- Structures - feeding place, cages, doors
Programming language:
- we will be writing pseudo-code
- I will use the syntax highlighting of java
- whatever concepts that we study today - will apply to any modern
programming language that support Object Oriented Programming
- Python
- C++
- Javascript (Typescript)
- Ruby
- Php
- C#
- Java / Kotlin
- Dart
-----------------------------------------------------------------------------
Master topics like Object Oriented Programming
- resources
- career related questions
- resume building
-----------------------------------------------------------------------------
🎨 Design a Character
======================
```java
class ZooCharacter {
// attributes - properties (data members)
// Staff
String staffName;
Gender staffGender;
Integer age;
Double salary;
String designation;
String department;
// ...
// Animal
String name;
Gender gender;
Integer age;
Boolean eatsMeat;
Boolean canFly;
// Visitor
String name;
Gender gender;
Ticket ticket;
Boolean isVIP;
DateTime timeOfArrival;
// methods - behavior
// Staff
void sleep();
void cleanPremises();
void eat();
void poop();
void feedAnimals();
// Animal
void sleep();
void eat();
void poop();
void fly();
void fight();
void eatTheVisitor();
// Visitor
void roamAround();
void eat();
void petAnimals();
void getEatenByAnimals();
class ZooCharacterTester {
void testAnimalEat() {
ZooCharacter animal = new ZooCharacter(...);
animal.eat();
Major Issues
Name collisions for staff/visitor/animal
- that is easy to fix - not a major issue
- we can just rename
- bad solution
- but okay, for now this will do
❓ Readable
Yes, I can read and understand this code.
However, as the complexity increases
- multiple types of staff
- gatekeeper
- ticket issuer
- cleaning staff
- feeding staff
- doctors
- categories of visitors
- free ticket
- paid ticket
- premium ticket
- underage - they must be accompanied by an adult
- group of visitor - school visit
So as the complexity grows, this code becomes extremely difficult to read and
understand.
BEcause I have to look at so many different behaviors and things
❓ Testable
Yes, I can write testcases for this!
However if someone modifies the bhevaior of staff/visitor, that could (by
mistake) effect the behaviour of animal
because all the attributes are shared - within the same class we have all
these attributes and methods
❓ Extensible
Will focus on this later
❓ Maintainable
If we have multiple devs working on different things
- Siddharth - Animal
- Vishnu - Staff
- Naved - Visitors
When they push their code - merge conflict!
because all these devs are working on the same class and the same file
🤔 What is the main reason for all these issues?
This class is doing too much work - it has wayy too many responsibilities
==================================
⭐ Single Responsibility Principle (SRP)
==================================
What feature of OOP can we use to break a large class into multiple classes?
Inheritance!
```java
class ZooCharacter {
String name;
Integer age;
Gender gender;
void eat();
void poop();
void sleep();
}
void cleanPremises();
}
void roamAround();
}
void eatTheVisitor();
}
```
OOP - used when the developer time is more expensive than the CPU time
- 99% of the cases, the dev is more valuable
- 1% cases (high freq trading / building games / rendering video /
machine learning) the CPU time is more valuable - do NOT use SOLID principles
here
Did we improve on any of the metrics? Did we solve any of the issues?
❓ Readable
So yes, there's a lot of files now, but each of those is small, and easy
to read and understand
❓ Testable
If I make a change in the Animal class, can these changes effect the
behavior of the Staff class?
No! This means that the testcases are more robust now! Less coupling of
the code!
❓ Extensible
(Will come back to this later)
❓ Maintainable
We will have reduced the merge conflicts because diff devs are working on
different files
-----------------------------------------------------------------------------
NEVER.
Requirements => develop code => Requirements change => .... => it's a never
ending process!
-----------------------------------------------------------------------------
🐦 Design a Bird
================
```java
void fly() {
}
}
```
```java
class Bird extends Animal {
// inherits the attributes from parent classes
// String species;
void fly() {
if (species == "sparrow")
print("flap wings and fly low")
else if (species == "pigeon")
print("fly on top of people and poop on their heads")
else if (species == "eagle")
print("glide elegantly high above")
}
}
```
- Readable
- Testable
- Maintainable
- Extensible - FOCUS!
```java
void fly() {
if (species == "sparrow")
print("flap wings and fly low")
else if (species == "pigeon")
print("fly on top of people and poop on their heads")
else if (species == "eagle")
print("glide elegantly high above")
}
}
}
[MyCustomGame] {
import PublicZooLibary.Animal;
import PublicZooLibary.Bird;
// .. import other things from external Libraries
class MySimpleZooGame {
void main() {
Bird b = new Bird(...);
b.fly();
}
```
No we don't. Most of the time we don't even have the source code
- libraries can be shipped in compiled form
(.so .dll .com .jar .war .pyc ...)
- even if you have the source code, you might not have modification
access to it
=======================
⭐ Open Close Principle
=======================
- Developer
- write code on their laptop
- test it locally
- commit & push & generate a Pull Request (PR)
- PR will go for review
- other devs in the team will suggest improvements
- the dev will go and make changes to make those improvements
- they will re-raise the PR
- re-review
- (iterative process)
- PR gets merged
- Quality Assurance
- write extra unit tests
- write integration tests
- UI: manual testing
- update the docs
- Deployment Pipeline
+ Staging servers
- will ensure that the code doesn't break
- there will be a lot of testing (unit/integration) & stress-testing
+ Production servers
* A/B test
- deploy to only 5% of the users
- we will monitor a lot of metrics
- number of exception
- customer satisfaction
- number of purchases
- ...
* Deploy to 100% of the userbase
In large companies it can take upto a month for a piece of code to go through
this pipeline
We DO NOT want the same piece of code to go through all this again
Only new code should go though this
```java
[MyCustomGame] {
import PublicZooLibary.Animal;
import PublicZooLibary.Bird;
import PublicZooLibary.Sparrow;
// .. import other things from external Libraries
class MySimpleZooGame {
void main() {
Bird sparrow = new Sparrow(...);
sparrow.fly();
}
```
❔ Isn't this the same thing that we did for Single Responsibility as well?
No. The solution was the same, but the intent/benefit was different
🔗 All the SOLID principles are tightly linked together. If you adhere to
one, you might get others for free!
-----------------------------------------------------------------------------
SDE => SDE 2 => SDE 3 => Staff Enginner (10+ YOE)
Because "good" developers are able to anticipate future changes and write
code today that doesn't need to be modified for those future changes!
(if you don't understand a topic, will you be able to realize when someone is
teaching you incorrectly? No)
Can you implement Builder Pattern in Python
- You should NEVER implement the builder pattern in python
- why?
- because the builder pattern provides the following
- named arguments
- positional arguments
- optional arguments
- validation of arguments
- builder pattern makes sense in Java
- C++ / Python / Ruby / Kotlin / JS - all these features are provided out
of the box
Always have a mentor who know what the correct thing is and can guide you on
the correct thing
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
```java
```
>
> ❓ How do we solve this?
>
> • Throw exception with a proper message
> • Don't implement the `fly()` method
> • Return `null`
> • Redesign the system
>
🏃♀️ Run away from the problem - Simply don't implement the `Kiwi.fly()` method
```java
void poop() {
print(...)
}
}
```
🐞 Compiler Error!
Because the term "abstract" enforces a "contract"
"abstract" = incomplete (missing details)
First, some class must "extend" bird and supply the missing functionality
`abstract void fly()`
Only then, I can instantiate from that child class
Either the class Kiwi should implement the abstract method `void fly()`
or, the class Kiwi itself should be marked as abstract (incomplete)
```java
void fly() {
throw new FlightlessBirdException("Bro, I'm a kiwi, I can't fly")
}
}
```
🐞 Violates Expectations!
```java
class ZooGame {
Bird getBirdObjectFromUserSelection() {
// go through all the bird classes
// show the various birds to the user in a nice UI
// the user will select one bird type
// we will create an object of that bird type
// return that object
void main() {
Bird b = new getBirdObjectFromUserSelection()
b.fly()
}
}
```
✅ Before extension
```java
class Kiwi extends Bird {
void fly() {
throw new FlightlessBirdException("Bro, I'm a kiwi, I can't fly")
}
}
```
❌ After extension
Violates Expectations!
==================================
⭐ Liskov's Substitution Principle
==================================
- Indian intuition:
- parents - set expectations
- child
- fullfill the expectations
- child not NOT violate the expectations set by the parent
- A child class should NOT violate the expectations set by the parent class
```java
interface ICanFly {
// allows me to "standardize" the API
void fly();
}
```
```py
class Bird(ABC):
@abstractmethod
def eat(self):
...
@abstractmethod
def poop(self):
...
class FlightMixin(ABC):
@abstractmethod
def fly(self):
...
class Sparrow(Bird, FlightMixin):
def eat(self):
print(...)
def poop(self):
print(...)
def fly(self):
print(...)
class Kiwi(Bird):
def eat(self):
print(...)
def poop(self):
print(...)
```
- Earlier the `abstract class Bird` had `abstract void fly()`, but now we
moved it to the interface
- This is not modification
- we expect the developer to follow the LSP from the very start
We're not modifying code, we're asking the dev to write better code from the
get go.
```java
interface ICanFly {
// allows me to "standardize" the API
void fly();
}
class ZooGame {
Bird getBirdObjectFromUserSelection() {
// go through all the bird classes
// show the various birds to the user in a nice UI
// the user will select one bird type
// we will create an object of that bird type
// return that object
void main() {
Bird b = new getBirdObjectFromUserSelection()
// b.fly() // compiler error
}
}
```
-----------------------------------------------------------------------------
```java
interface ICanFly {
void fly();
void flapWings();
void kickToTakeOff();
}
class Sparrow extends Bird implements ICanFly {
void fly()
}
void flapWings() {
// SORRY Shaktiman!
}
}
```
>
> ❓ Should these additional methods be part of the ICanFly interface?
>
> • Yes, obviously. All things methods are related to flying
> • Nope. [send your reason in the chat]
>
Not really!
==================================
⭐ Interface Segregation Principle
==================================
```java
interface DatabaseCursor {
List<Row> find(String query)
List<Row> insert(String query)
List<Row> delete(String query)
List<Row> join(String query, String table1, String table2)
}
class SQLDatabaseCursor {
List<Row> find(String query)
List<Row> insert(String query)
List<Row> delete(String query)
class MongoDatabaseCursor {
List<Row> find(String query)
List<Row> insert(String query)
List<Row> delete(String query)
```
❓ Isn't this similar to LSP? Isn't this just SRP applied to interfaces?
-----------------------------------------------------------------------------
multiple inheritance
multi-level inheritance
abstract class vs interface (java)
abstract class vs mixin (python)
-----------------------------------------------------------------------------
🗑️ Design a Cage
================
```java
void feedAnimals() {
for(Tiger kitty: this.kitties)
// delegate the feeding task to the dependency
this.bowl.feed(kitty)
}
class Cage2 {
// cage for Pigeons
void feedAnimals() {
for(Pigeon tweety: this.tweeties)
// delegate the feeding task to the dependency
this.bowl.feed(tweety)
}
class ZooGame {
void main() {
// create a cage for tigers
Cage1 tigerCage = new Cage1();
```
```
```
=================================
⭐ Dependency Inversion Principle - the guideline
=================================
```
------- --------- -------
IBowl Animal IDoor High Level
------- --------- ------- abstraction
│ │ │
╰───────────────╁──────────────╯ (direct dependence)
┃
┏━━━━━━┓
┃ Cage ┃ some piece of code
┗━━━━━━┛
```
But how?
=======================
💉 Dependency Injection - how to achieve that guideline
=======================
```java
IDoor door;
IBowl bowl;
List<Animal> animals;
class ZooGame {
void main() {
// create a cage for tigers
Cage tigerCage = new Cage(
new IronDoor(...),
new MeatBowl(...),
Arrays.asList(new Tiger("Simba"), new Tiger("Musafa"))
);
Enterprise Code
===============
================
🎁 Bonus Content
================
>
> We all need people who will give us feedback.
> That’s how we improve. 💬 Bill Gates
>
-------------
🧩 Assignment
-------------
https://github.com/kshitijmishra23/low-level-design-
concepts/tree/master/src/oops/SOLID/
----------------------
⭐ Interview Questions
----------------------
>
> ❓ Which of the following is an example of breaking
> Liskov Substitution Principle?
>
> A) A subclass that overrides a method of its superclass and changes
> its signature
>
> B) A subclass that adds new methods
>
> C) A subclass that can be used in place of its superclass without
> any issues
>
> D) A subclass that can be reused without any issues
>
> ❓ How can we achieve the Interface Segregation Principle in our classes?
>
> A) By creating multiple interfaces for different groups of clients
> B) By creating one large interface for all clients
> C) By creating one small interface for all clients
> D) By creating one interface for each class
> ❓ Which SOLID principle states that a subclass should be able to replace
> its superclass without altering the correctness of the program?
>
> A) Single Responsibility Principle
> B) Open-Close Principle
> C) Liskov Substitution Principle
> D) Interface Segregation Principle
>
>
> ❓ How can we achieve the Open-Close Principle in our classes?
>
> A) By using inheritance
> B) By using composition
> C) By using polymorphism
> D) All of the above
>
# ============================ That's all, folks! ===========================