SOLID Principles
SOLID Principles
=======================
Pragy
Senior Software Engineer + Instructor @ Scaler
https://linktr.ee/agarwal.pragy
❓ 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%
>
I want to have high productivity during my "work" time - so that I don't have to
spend a lot of in it
✅ Goals
========
1. Readable
2. Maintainable
3. Testable
4. Extensible
#### 🧓🏻
===================
💎 SOLID Principles
===================
- Single Responsibility
- Open/Closed
- Liskov's Substitution
- Interface Segregation
- Dependency Inversion
💭 Context
==========
- "toyish" example - captures the nuances, without unnecessary complexity
- Build a Zoo Game 🦊
- characters
- structures
Programming language:
- not going to follow any particular programming language
- pseudo-code
- looks like modern java, but will not compile
- something that you can read & understand
- the concepts that we study will apply to any modern programming language that
supports OOP
- python
- java / kotlin
- javascript/typescript
- ruby
- php
- c++
- c#
-----------------------------------------------------------------------------
🎨 Design a Character
======================
Visitors - buy tickets, loiter around, see animals, eat, make noise, throw
garbage..
Animals - eat food, eat the visitors, hide in their cage, get aggressive, ..
Staff - clean, feed the animals, do staffy stuff
```java
// THE CODE BELOW IS SHITTY CODE - ITS HORRIBLE
// DO NOT WRITE CODE LIKE THIS!
class ZooActor {
// properties [attributes]
// visitor
String visitorName;
String visitorAddress;
Integer visitorAge;
String visitorPhone;
Integer visitorTicketID;
// staff
String staffName;
String staffAddress;
Integer staffAge;
String staffPhone;
Integer staffDepartment;
Integer staffSalary;
// animal
String animalName;
Integer animalWeight;
Integer animalAge;
String animalSpecies;
String animalIsCarnivorous;
// methods [behavior]
// visitor
void roam();
void lookAtAnimals();
void eat();
void poop();
Ticket bookTicket();
// staff
void feedAnimals();
void cleanPremises();
void eat();
void poop();
void checkIn();
// animal
void roam();
void lookAtVisitors();
void eat();
void poop();
void eatTheVisitors();
}
class ZooActorTester {
void testAnimalEating() {
ZooActor animal = new ZooActor(...);
animal.eat();
// ensure that the correct behavior is displayed
}
}
```
- name collisions
"name" exists for both staff, and visitor, and animal
this is a non-issue, because I can simply rename my variables
Major Issues
❓ Readable
This code is readable right now
But, as the number of actors increase (staff categories / VIP visitors / add
animal species) this code will quickly become unreadable!
❓ Testable
Yes, I can test this code.
Because all the state is "shared" across all actor types, changing the
behavior/code/logic for one can incorrectly effect the code/logic for other actors
Tests will break all the time
Very hard to write comprehensive tests & keep them up to date
❓ Extensible
We'll come back to this
❓ Maintainable
If the dev team is large, and multiple devs are working on different features
- someone is working on visitors
- someone is working on animals
they will end up modifying the same class & same file
when they try to submit the changes, they will incur - merge conflicts!
==================================
⭐ Single Responsibility Principle
==================================
- if you see a piece of code that violates the SRP, then you should break it down
into multiple pieces
- we can use OOP tool "inhertance" to split this code
```java
class ZooActor {
String name;
Integer age;
Integer weight;
void eat();
void poop();
}
void roam();
void lookAtVisitors();
void eatTheVisitors();
}
class Visitor extends ZooActor {
String address;
String phone;
Integer ticketID;
void roam();
void lookAtAnimals();
Ticket bookTicket();
}
class Staff extends ZooActor {
String address;
String phone;
Integer department;
Integer salary;
void feedAnimals();
void cleanPremises();
void checkIn();
}
```
It's just not enough to make changes - we need to ensure that the changes are
infact better!
Did we improve on our metrics?
❓ Readable
Aren't there wayy too many classes now?
- Yes, there are.
- But that does NOT make the code less readable
- at any given time, any developer will only be working with 1 or a few
classes/files
- each of those classes/files is small, and easily readable
❓ Testable
Can a change made inside Visitor class effect (by mistake) the behavior of
Animal class?
No!
❓ Extensible
We'll come back to this later
❓ Maintainable
If diff devs are working on different features (visitor / animal)
will they have merge conflicts?
significantly reduced!
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
🐦 Design a Bird
================
```java
// Akash is building this
// Akash is already familiar with the single responsibility principle
// but he doesn't have enough time right now
void fly() {
// Akash needs to implement this function
}
}
```
```java
void fly() {
if (species == "Sparrow")
print("fly low, close to the ground")
else if (species == "Pigeon")
print("fly, and poop on people below while flying")
else if (species == "Eagle")
print("glide high above the clouds elegantly")
}
}
```
- Readable
- Testable
- Maintainable
- Extensible - FOCUS!
Is this code extensible? Can I add a new type of bird to this code?
Yes, easily!
Just add another `else if` condition
```java
else if (species == "Peacock")
print("pe-hens(females) can fly, but males cannot")
```
```java
void fly() {
if (species == "Sparrow")
print("fly low, close to the ground")
else if (species == "Pigeon")
print("fly, and poop on people below while flying")
else if (species == "Eagle")
print("glide high above the clouds elegantly")
}
}
}
[MyCustomGame] { // Srihari is using the above library to build his own Zoo Game
import PublicZooLibary.Bird;
class SriZooGame {
void main() {
Bird tweety = new Bird(species="sparrow");
tweety.fly();
}
}
// Srihari now wants to add a new type of bird and make it fly
}
```
=======================
⭐ Open/Close Principle
=======================
- Your code should be closed for modification, yet, it should remain open to
extension!
> how is this even possible? cannot modify, but can still extend?
```java
[PublicZooLibary] // Akash wrote this library and published it on githuhb
// Akash made sure to follow Open/Close principle
{
[MyCustomGame] { // Srihari is using the above library to build his own Zoo Game
import PublicZooLibary.Bird;
class SriZooGame {
void main() {
Bird tweety = new Bird(species="sparrow");
tweety.fly();
}
}
// Srihari now wants to add a new type of bird and make it fly
class Peacock extends Bird {
void fly() { print("pe-hens(females) can fly, but males cannot") }
}
}
```
❔ Isn't this the same thing that we did for Single Responsibility as well?
Yes, in both cases, we took a large class and broke it into smaller classes
by using inheritance
🔗 All the SOLID principles are tightly linked together! If you adhere to one, you
might get others for free!
-----------------------------------------------------------------------------
Good engineers are able to anticipate future requirements and develop solutions
today that do not need (major) changes for the future requirements.
-----------------------------------------------------------------------------
How can I become someone like that? Someone who can demand a salary of 3 Cr?
You need to upskill yourself
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
```java
abstract class Bird extends Animal {
// String species; inherited from the parent class
Integer beakLength;
void fly() {
// Kiwi cannot fly!
// return null;
throw new FlightlessBirdException("Kiwi's can't fly bro!");
}
}
```
>
> ❓ How do we solve this?
>
> • Throw exception with a proper message
> • Don't implement the `fly()` method
> • Return `null`
> • Redesign the system
>
🏃♀️ Run aaway from the problem - Don't implement the `void fly` at all!
```java
abstract class Bird extends Animal {
abstract void fly();
}
class Kiwi extends Bird {
// we have not implemented void fly!
}
```
🐞 Compiler Error!
Abstract class = incomeplete class
Abstract method = contract that says "either a child class should supply the
imeplementation of this function, or the child child will itself be incomplete"
Either class Kiwi should provide an implementation of void fly, or class Kiwi
should be abstract itself!
```java
abstract class Bird extends Animal {
abstract void fly();
}
class ZooGame {
Bird getBirdTypeFromUserSelection() {
// shows a nice GUI to the user
// it shows various bird species - abilities, weakness, food type, ..
// user selects one species
// function creates an object of that species
// and returns it
void main() {
Bird b = getBirdTypeFromUserSelection();
b.fly();
}
}
```
🐞 Violates Expectations
```java
// an intern joins the company, and the write the following code
Bird getBirdTypeFromUserSelection() {
// shows a nice GUI to the user
// it shows various bird species - abilities, weakness, food type, ..
// user selects one species
// function creates an object of that species
// and returns it
// note:
// Bird getBirdTypeFromUserSelection() can now also return Kiwis
// we will get an exception in the main class
void main() {
Bird b = getBirdTypeFromUserSelection();
b.fly(); // if this happens to be a Kiwi object, we will get an exception
here
}
}
```
✅ Before extension
- code works
- client happy
- dev happy
❌ After extension
- did the intern touch existing code?
No
- does the original dev of the code know about the addition that the intern
made?
Not necessarily
- was the code working before the intern added their stuff?
Yes
- does the code work now?
No
- does the intern's code work?
Yes
- does the old code work?
No.
==================================
⭐ Liskov's Substitution Principle
==================================
- (bording definition) Any object of a `class Parent` should be replacable with any
object of a `class Child extends Parent` without any issues
- (intuition)
- parents set the expectations
- children are supposed to meet those expecations
- if a child doesn't meet the parent's expectations - chappal
A child should not violate their parent's expectations
🎨 Redesign the system!
Use an interface (python: continue using abstract base class, c++: continue using
pure virtual methods)
```java
abstract class Bird extends Animal {
int beakLength;
int weight;
interface ICanFly {
void fly();
}
class ZooGame {
Bird getBirdTypeFromUserSelection() {
// shows a nice GUI to the user
// it shows various bird species - abilities, weakness, food type, ..
// user selects one species
// function creates an object of that species
// and returns it
// note:
// Bird getBirdTypeFromUserSelection() can now also return Kiwis
// we will get an exception in the main class
void main() {
Bird b = getBirdTypeFromUserSelection();
if(b instanceof ICanFly) {
ICanFly flyingB = (ICanFly) b;
flyingB.fly();
}
b.speak();
}
}
```
```py
class Bird(ABC):
@abstractmethod
def speak(self):
raise NotImplementedError()
...
class FlightMixin(ABC):
@abstractmethod
def fly(self):
raise NotImplementedError()
def fly(self):
print('flap flap')
class Kiwi(Bird):
def speak(self):
print('chirp chirp')
```
-----------------------------------------------------------------------------
```java
interface ICanFly {
void fly();
void spreadWings() {
print("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]
>
==================================
⭐ Interface Segregation Principle
==================================
- Your clients should not be forced to implement methods that they do not need
❓ Isn't this similar to LSP? Isn't this just SRP applied to interfaces?
🔗 Yes
But it also applies to other interfaces - like REST APIs
Zeta
- class constructer had 300 parameters
```java
interface ICanFly {
void fly();
}
interface IHasWings {
// when a Bird is flying what steps does it take?
void spreadWings();
void flapWings();
}
```
-----------------------------------------------------------------------------
🗑️ Design a Cage
================
```java
public Cage1() {
kitties.add(new Tiger("simba"));
kitties.add(new Tiger("musafa"));
kitties.add(new Tiger("sharekhan"));
}
void feed() {
// delegate to the bowl
for(Tiger t: this.kitties)
this.bowl.feed(t);
}
class Cage2 {
// for pigeons
public Cage1() {
poopies.add(new Pigeon("piggy 1"));
poopies.add(new Pigeon("piggy 2"));
poopies.add(new Pigeon("piggy 2"));
}
void feed() {
// delegate to the bowl
for(Pigeon p: this.poopies)
this.bowl.feed(p);
}
// ...
class ZooGame {
void main() {
Cage1 kittyCage = new Cage1();
Cage2 poopyCage = new Cage2();
// now if I wish to add another cage, I need to first create a new class
// client (main function) has no control
}
}
```
```
```
=================================
⭐ Dependency Inversion Principle - what to do
=================================
```
------- --------- -------
IBowl Animal IDoor high level abstractions
------- --------- -------
│ │ │
╰───────────────╁──────────────╯
┃
┏━━━━━━┓
┃ Cage ┃ controller
┗━━━━━━┛
```
But how?
=======================
💉 Dependency Injection - how to achieve the principle
=======================
```java
class Cage {
// generic class - can hold anything
IDoor door;
List<Animal> animals;
class ZooGame {
void main() {
Cage kittyCage = new Cage(
new MeatBowl(),
new IronDoor(),
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
>