SOL Princi
SOL Princi
=======================
Pragy
Senior Software Engineer + Instructor @ Scaler
https://linktr.ee/agarwal.pragy
💎 Agenda
=========
🎧 Audio/Video issues
Disable Ad Blockers & VPN. Check your internet. Rejoin the session.
💡 Prerequisites?
Basics of Object Oriented Programming
-----------------------------------------------------------------------------
✅ Goals
========
>
> ❓ What % of your work time is spend writing new code?
>
> • 10-15% • 15-40% • 40-80% • > 80%
How to maximize my off-time? By ensuring that whatever work I do, I do it right the first time!
1. Readable
2. Testable
3. Maintainable
4. Extensible
===================
💎 SOLID Principles
===================
💭 Context
==========
Create a search engine like Google.com - can I complete this in a 3 hour masterclass?
No!
Curated example / toy problem - specifically designed to understand the concepts while minimizing complexity
- Game - Zoo 🦊
- Characters
- Animals - birds, reptiles, humans, ..
- Zoo Staff
- Visitors
- Infrastructure
- Cages
- Feeding places
- Roads
-----------------------------------------------------------------------------
```java
class ZooEntity {
// attributes / properties / data
// Staff
String name;
Gender gender;
Integer age;
Double salary;
String designation;
String department;
// ...
// Animal
String name;
Gender gender;
Integer age;
Boolean eatsMeat;
Boolean canFly;
Boolean hasWings;
String species;
// Visitor
String name;
Gender gender;
Ticket ticket;
Boolean isVIP;
DateTime timeOfArrival;
// methods / behaviour
// 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();
void litter();
}
class ZooEntityTester {
void testAnimalEats() {
ZooEntity animal = new ZooEntity(...);
animal.eat(); // assert that something happens
}
}
```
❓ Readable
Seems like it - I can read & understand this code
Issues:
- lengthy
- too much going on - it's a jumbled mess!
- understanding the code is difficult because I've to keep track of so many variables
❓ Testable
I can write testcases for it
Issues:
- changing the behaviour of animal can change the behavior of staff/visitor by mistake - because all
the variables are shared
- this makes testing difficult!
- code is "tightly coupled"
❓ Extensible
will come back to this later
❓ Maintainable
dev 1 - staff
dev 2 - visitors
dev 3 - animals
if all 3 devs make changes and push their code - merge conflicts!
if a new dev has to onboarded - they must be taught everything about animal+staff+visitor even if they
want to work only on animals
==================================
⭐ Single Responsibility Principle
==================================
- Any function/class/module/package (unit-of-code) should have only 1, well-defined responsibility
- only 1: not more
- well defined: should not be vague
- if some piece of code doesn't follow the SRP, then we should split it into multiple parts.
```java
class ZooCharacter {
String name;
Integer age;
Gender gender;
void eat();
void poop();
void sleep();
}
void cleanPremises();
}
void roamAround();
}
❓ Readable
❓ Testable
If I make a change to class Animal, can that effect (by mistake) the working of the Staff class?
No!
Code has been de-coupled
Code is easier to test! I can test each component separately!
❓ Extensible
will come back to this later
❓ Maintainable
merge conflicts: significantly reduced!
onboard a new dev: easier to onboard
🐦 Design a Bird
================
```java
class Bird extends Animal {
String species; // inherited from Animal
void fly() {
}
}
```
```java
class Bird extends Animal {
String species; // inherited
void fly() {
if (species == "sparrow")
print("flap wings vigorously and make noise")
else if (species == "pigeon")
print("fly above your head and poop on you")
else if (species == "eagle")
print("glide elegantly high up in the clouds")
}
}
```
- Readable
- Testable
- Maintainable
- Extensible - FOCUS!
```java
[PublicZooLibary] {
// some author has coded this library and put it on github
class Animal {
String species;
}
[MyCustomGame] {
import PublicZooLibary.Bird;
class AwesomeZooGame {
void main() {
Bird b = new Bird("pigeon");
b.fly();
}
}
}
```
```java
else if (species == "new bird type here")
print("custom fly logic here ... ")
```
=======================
⭐ Open/Close Principle
=======================
- Code should be open for extension, yet, it should remain closed for modification!
```java
[PublicZooLibary] {
// some author has coded this library and put it on github
class Animal {
String species;
}
[MyCustomGame] {
import PublicZooLibary.Bird;
class AwesomeZooGame {
void main() {
Peacock p = new Peacock();
p.fly();
}
}
}
```
OCP tells you that you will be able to add new features without modifying existing code, if you design
for extensibility from the start!
❔ Isn't this the same thing that we did for Single Responsibility as well?
SRP - large ZooEntity class => broke it down into smaller subclasses
OCP - large Bird class => broke it down into smaller subclasses
No!
Solution was similar, but the intent was different
-----------------------------------------------------------------------------
## Salary
A good developer can anticipate future requirements and changes, and write code today that doesn't need to
be modified for those new features!
- SOLID Principles
- Design Patterns
- Structural / Behavioral / Creational
- builder / singleton / factory / strategy / adapter / proxy / ...
- Case Studies
- Tic Tac Toe
- Chess
- Snake Ladder
- Parking Lot
- Splitwise
- Library Management
- REST API
- idempotency
- naming conventions
Builder pattern
- works around language limitations
- java does not have the following features
- named arguments
- change the position of arguments
- validation for arguments
- default values for arguments
- you use builder pattern in java to work around these issues
### Solution
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 `void fly()`
```java
abstract class Bird {
abstract void fly();
}
🐞 Compiler Error! Either `class Kiwi extends Bird` must implement `void fly()` or it should itself be
marked as abstract
```java
abstract class Bird {
abstract void fly();
}
```java
abstract class Bird {
abstract void fly();
}
class ZooGame {
Bird getBirdObjectFromUserSelection() {
// if(userSelection == "pigeon")
// return new Pigeon(...)
// else if(userSelection == "sparrow")
// return new Sparrow(...)
void main() {
Bird b = getBirdObjectFromUserSelection()
b.fly()
}
}
```
```java
class Kiwi extends Bird {
void fly() {
throw new FlightlessBirdException("...")
}
}
```
❌ After extension
- Did the intern modify your existing code?
No. They didn't touch it - they just "extended" the code
- Are you (original dev) aware of the fact that someone has added a Kiwi class?
Most likely no!
- Was the original code working before this addition was made?
Yes it was
class ZooGame {
Bird getBirdObjectFromUserSelection() {
void main() {
Bird b = getBirdObjectFromUserSelection()
b.fly() // if b is a Kiwi, I will get an exception here!
}
}
```
- mathematical/type-theory definition: Any object of `class Parent` should be replacable with any object of
`class Child extends Parent` without causing any issues
- Indian intuition:
- parents have expectations from their children (doctor/engineer)
- child violates (cricketer)
- parents set expectations, and the children are supposed to satisfy those expectations.
LSP: Children should not violate the expectations set by the parents!
```java
// abstract void fly() // this should not be here - because not all birds can fly!
}
interface ICanFly {
void fly();
}
void fly() {
print(" ... ")
}
}
```
```py
from abc import ABC, abstractmethod
class Bird(ABC):
@abstractmethod
def eat(self): ...
class CanFlyMixin(ABC):
@abstractmethod
def fly(self): ...
def fly(self):
print('...')
class Kiwi(Bird):
def eat(self):
print('...')
# no need to implement void fly() here - no compiler error if you omit it
```
```java
// THIS IS A BAD SOLUTION!!
// Because it leads to Combinatorial explosion
// Imagine that instead of just Fly vs NoFly
// I have more such separations - Pecking vs NonPecking
// Running vs NonRunning
// I will have to implement all 2^N combinations
// PeckingNonFlyingBird
// NonPeckingNonFlyingRunningBird
// we prefer composition over inheritance
```java
// abstract void fly() // this should not be here - because not all birds can fly!
}
interface ICanFly {
void fly();
}
void fly() {
print(" ... ")
}
}
class ZooGame {
Bird getBirdObjectFromUserSelection() {
void main() {
Bird b = getBirdObjectFromUserSelection()
// b.fly() // compiler error! fly() does not exist in class Bird!
// explicit downcasting
if(b instanceof ICanFly) {
ICanFly flyingBird = (ICanFly) b;
flyingBird.fly();
}
}
}
```
-----------------------------------------------------------------------------
```java
interface ICanFly {
void fly();
void flapWings();
void spreadWings();
void jump();
}
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]
>
==================================
⭐ Interface Segregation Principle
==================================
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)
}
```
❓ Isn't this similar to LSP? Isn't this just SRP applied to interfaces?
Yes. The solution is same - but intents are different
```java
interface ICanFly {
void fly();
}
interface IHasWings {
void flapWings();
void spreadWings();
}
interface ICanJump {
void jump();
}
-----------------------------------------------------------------------------
🗑️ Design a Cage
================
```java
class Cage1 {
public Cage1() {
this.kitties.add(new Tiger("simba"));
this.kitties.add(new Tiger("musafa"));
this.kitties.add(new Tiger("scar"));
}
void feedAnimals() {
for(Tiger kitty: this.kitties)
this.bowl.feed(kitty) // delegate
}
}
class Cage2 {
void feedAnimals() {
for(Tiger kitty: this.peas)
this.bowl.feed(kitty) // delegate
}
}
class ZooGame {
void main() {
Cage1 tigerCage = new Cage1()
tigerCage.resistAttack(new Attack(...))
}
}
```
```
```
`class Cage1` depends on low level implementation details `MeatBowl`, `Tiger`, and `IronDoor`
=================================
⭐ Dependency Inversion Principle what to do
=================================
```
------- --------- -------
IBowl Animal IDoor high level
------- --------- ------- abstractions
│ │ │
╰───────────────╁──────────────╯
┃
┏━━━━━━┓
┃ Cage ┃ code
┗━━━━━━┛
```
But how?
=======================
💉 Dependency Injection how to achieve inversion
=======================
```java
class Cage {
// generic cage
void feedAnimals() {
for(Animals animal: this.animals)
this.bowl.feed(animal) // delegate
}
}
class ZooGame {
void main() {
Cage tigerCage = new Cage(
new MeatBowl(),
new IronDoor(),
Arrays.asList(new Tiger("simba"), new Tiger("musafa"))
);
}
}
```
-----------------------------------------------------------------------------
Backend
- SpringBoot (java)
- Rails (ruby)
- Django (python)
- Laravel (php)
Frontend
- React (js)
- Flutter (Dart)
- Angular (js)
-----------------------------------------------------------------------------
Enterprise Code
===============
-----------------------------------------------------------------------------
================
🎁 Bonus Content
================
-------------
🧩 Assignment
-------------
https://github.com/kshitijmishra23/low-level-design-concepts/tree/master/src/oops/SOLID/
-----------------------------------------------------------------------------
QnA
---
Interfaces don't have state (no variables) - they're just to enforce a contract/protocol/API
Abstract classes are basically interfaces with the additional ability to store data (variables)
-----------------------------------------------------------------------------
----------------------
⭐ 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
>
-----------------------------------------------------------------------------
>
> We all need people who will give us feedback.
> That’s how we improve. 💬 Bill Gates
>