Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

SOLID Principles

Download as pdf or txt
Download as pdf or txt
You are on page 1of 17

Important Points

================
💬 Communicate using the chat box

🙋 Post questions in the "Questions" tab

💙 Upvote others' question to increase visibility

👍 Use the thumbs-up/down buttons for continous feedback

⏱️ Bonus content at the end

❓ FAQ
======
▶️Will the recording be available?
To Scaler students only

✏️Will these notes be available?


Yes. Published in the discord/telegram groups (link pinned in chat)

⏱️ Timings for this session?


8pm - 11pm (3 hours) [15 min break midway]

🎧 Audio/Video issues
Disable Ad Blockers & VPN. Check your internet. Rejoin the session.

❔ Will Design Patterns, topic x/y/z be covered?


In upcoming masterclasses. Not in today's session.
Enroll for upcoming Masterclasses @ [scaler.com/events](https://www.scaler.com/events)

🖥️ What programming language will be used?


The session will be language agnostic. I will write code in Java.
However, the concepts discussed will be applicable across languages

💡 Prerequisites?
Basics of Object Oriented Programming

👨‍💻About the Instructor


=======================

Pragy
[linkedin.com/in/AgarwalPragy](https://www.linkedin.com/in/AgarwalPragy/)

Senior Software Engineer + Instructor @ Scaler

-----------------------------------------------------------------------------

>
> ❓ What % of your work time is spend writing new code?
>
> • 10-15% • 15-40% • 40-80% • > 80%
>

< 15%

⏱️ Where does the rest of the time go?

- learning & upskilling


- meetings
- designing and analyzing
- debugging
- reading other peoples' code / reading documentation / stackoverflow
- testing
- refactoring
- Knowledger transfer (KT)

- chai breaks / Table tennis / snooker => max

✅ Goals
========

We'd like to make our code

1. Extensible
2. Maintainable
3. Testable
4. Readable

#### Robert C. Martin 🧓🏻 - Uncle Bob

===================
💎 SOLID Principles
===================

- Single Responsibility
- Open Closed
- Liskov's Substitution (Barbara Liskov)
- Interface Segregation
- Depedency Inversion

Object Oriented / Open Closed


Interface Segregation / Inversion of Control
Dependency Inversion / Dependency Injection

💭 Context
==========

- Designing A Zoo game 🦊


- Various characters - Animals

-----------------------------------------------------------------------------

🐶 Design an Animal
===================

```java

class Animal {
// ideas / concepts in our mind => classes in OOP

// properties / attributes

String name;
Float age;
Float height;
String species;
String gender;
Color color;
Float lifeExpectancy;
Integer numberOfLegs;
String foodType;
Boolean isWild;
Boolean canSwim;

// behavior / methods

void speak() {}
void eat() {}

void walk() {}

void fly() {}

void breed() {}

```

🐠 Design the Speaking behavior - different animals will speak in different manners

```java

// pseudo-code - code in a made up language

class Animal {
String species;
// ...

void speak() {

if(weight >= 200)


print("speak in deep heavy voice") // elephants
if(species == "feline")
print("Meow")
else if (species == "tiger")
print("Rawwwrrr")
else if (species == "human")
print("Talk")
else if (species == "big cat")
print("Roar")
else if (species == "fox")
print("what does the fox say?")

}
}

class AnimalTester {
void testCatSpeak() {
Animal kitty = new Animal();
kitty.species = "cat";
kitty.speak() // assert that this prints "Nyan"
}
}

```

🐞 Problems with the above code?

❓ Readable
I can defintely read this code & understand it.
If I have 100 species, then will I be able to understand this code? No
I have to carefully analyse each condition before claiming how the code behaves

❓ Testable
I can totally write testcases - is testable
Code is coupled - changing the behavior of 1 species might effect the behavior of another species without
intention

❓ Extensible
I can totally add a new else-if condition if I need to add a new species
Adding extra functionality is difficult
We will revisit this part later

❓ Maintainable
Multiple devs can work on this codebase
All working on the same file, same class, same function - merge conflicts
🛠️ How to fix this?

==================================
⭐ Single Responsibility Principle
==================================

- Every class/function/module/unit-of-code should have exactly 1, well-defined responsibility

- Any unit-of-code should have only 1 reason to change

- if you identify that there is a piece of code which is serving multiple responsibilities - break the code
down into smaller pieces

```java

abstract class Animal {


String species;

abstract void speak();


}

class Tiger extends Animal {


void speak() {
print("Rawrr")
}
}

class Human extends Animal {


void speak() {
print("Talk")
}
}

class Bird extends Animal {


...
}

```

- Readable
If we have 100 species, then we will now have 100 classes!
1. This code can be reduced by metaprogramming (generics, templates, macros, reflection, preprocessor,
decorators ...)
2. Each file is small and easy to read
- as a developer, you will be working with a handful of files at any given time

- Testable
Each of the species is testable separately. Code is now decoupled

- Maintainable
Different devs working on different species will not lead to merge conflicts

-----------------------------------------------------------------------------

🐦 Design a Bird
================

```java

class Animal {
String species;
}

class Bird extends Animal {


void fly()
}
```

🕊️ Different birds will fly in different manners

```java

[library] MySimpleZoo {
// code provided by the above library
// executable - .com .dll .o .so .class .jar - source code is not available
// open source - it might be someone else's code, and you don't have the permission to modify it

class Animal {
String species;
}

class Bird extends Animal {


void fly() {
// species is inherited from parent class Animal

if(species == "sparrow")
print("fly indoors")
else if (species == "eagle")
print("fly elegantly above the clouds")
else if (species == "penguin")
print("dude, I can't really fly")
}
}

[my executable] MyAwesomeZooGame {

import MySimpleZoo.Animal;
import MySimpleZoo.Bird;

// I (the user of the library code) wish to add new functionality to the Bird class - add a new species
"Duck"

class MyAwesomeZooGame {
void main() {
Bird b = new Bird("sparrow");
b.fly();
}
}

```

Akshay Shinde - CTO of Zerodha


- create a library that our users can use to automate their trading strategies

🐞 Problems with the above code?

- Readable
- Testable
- Maintainable

- Extensible - FOCUS!
If I don't have modification permissions to some piece of code, I will not be able to add new functionality
to that code.

🛠️ How to fix this?

=======================
⭐ Open-Close Principle
=======================

- Code should be closed for modification, but still, open for extension!

❔ Why is modification bad?

- Developer - write code, debug & test it on local machine


- Submit a Pull Request (PR) - iterations with team - make modifications - finally it will be accepted &
merged
- QA team - check existing testcases - write new testcases from scratch - end-to-end testing
- go ahead from QA team - ready to be deployed
+ staging servers - we will check if any of the functionality breaks
+ deployed to the users!
* A/B tested - 5% of the userbase
- monitor exception, metrics .. make sure that there is no unexpected change in the user behavior
* 100 % of the userbase

```java

[library] MySimpleZoo {
// code provided by the above library
// executable - .com .dll .o .so .class .jar - source code is not available
// open source - it might be someone else's code, and you don't have the permission to modify it

class Animal {
String species;
}

abstract class Bird extends Animal {


abstract void fly();
}

class Sparrow extends Bird {


void fly() {
print("fly indoors")
}
}

class Eagle extends Bird {


void fly() {
print("fly elegantly above the clouds")
}
}
}

[my executable] MyAwesomeZooGame {

import MySimpleZoo.*;

// I (the user of the library code) wish to add new functionality to the Bird class - add a new species
"Duck"

class Duck extends Bird {


void fly() {
print("I can both swim and fly!")
}
}

class MyAwesomeZooGame {
void main() {
Bird b = new Bird("sparrow");
b.fly();
}
}

```

- Extension
We can extend this code freely - without having to modify the library that we don't have the source-
code/edit-persmissions for!
❔ Didn't we do the same thing to fix the non-SRP and non-OC code?
Yes, certainly! Exact same resolution

❔ Does this mean that the Single Responsibility Principle (SRP) == Open-Close Principle (OCP)
No - the intention was different. The solution was same

🔗 All the SOLID principles are linked together

-----------------------------------------------------------------------------

3 hour masterclass - code everything in the Low Level Design topics in this?

What is important - to be a good developer

- Object Oriented Programming


- classes, objects, abstractions, encapsulation, runtime polymorphism
- SOLID Principles
- Design Patterns
- behavioral, structural, creational - builder, strategy, factory, singleton, ..
- Case studies - Desgin a TicTacToe game, Snake & Ladder, Parking Lot, Splitwise, ..
- Machine Coding / Take-home assignments

DSA/SQL/Networks/Memory/Concurrency/LLD/HLD/...

a success metric for an online upskilling course - before and after of the career
most measureable thing is $$$$$$$$

I will teach you for 11 months and after that you will feel really happy

Scaler has had > 50,000 alumni so far


92% of our alumni have been placed
- what about the remaining 8%
- some people who don't want to apply for jobs - because they're at CxO positions
- some people who want to go for higher studies
- some people who simply didn't work hard - 3%
Median salary that we have is 2x.xxx lakhs
- averages are misleading
- bar with 100 people, all of them are bankrupt. Elon Musk enters the bar - everyone in the bar is now
worth 3 Billion $ on average

-----------------------------------------------------------------------------
resuming class at 9.55 pm sharp!
-----------------------------------------------------------------------------

Software salaries in India - they go upto 3 Cr per annum


Talk to any SDE3+ or staff engineer position at a tier-1 company

🐓 Can all Birds fly?


=====================

```java

abstract class Bird {


abstract void fly();
}

class Sparrow extends Bird {


void fly() {
print("fly indoors")
}
}

class Eagle extends Bird {


void fly() {
print("fly high above the clouds")
}
}

class Kiwi extends Bird {


void fly() {
!? // kiwi doesn't fly!
}
}

```

Kiwi, Ostrich, Penguins, Dodo, Emu ... are all birds that can't fly

>
> ❓ 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 - don't implement the fly method!

```java

abstract class Bird { // abstract class => incomplete class


abstract void fly();
}

class Kiwi extends Bird {


// I simply don't implement the void fly()

// void fly() {
// !? // kiwi doesn't fly!
// }
}

```

🐞 This will lead to a compiler error. Compiler says that either implement void fly inside class Kiwi, or
mark class Kiwi as abstract class too

⚠️Throw an exception

```java

abstract class Bird {


abstract void fly();
}

class Kiwi extends Bird {


void fly() {
throw new UserIsAStupidPersonException("Dude, Kiwi's can't fly, even class 5th students know that.");
}
}

```

🐞 This code violates expectations

```java
// Old Code

abstract class Bird {


abstract void fly();
}
class Sparrow extends Bird {
void fly() {
print("fly indoors")
}
}

class Eagle extends Bird {


void fly() {
print("fly high above the clouds")
}
}

class ZooGame {

Bird getBirdFromUserChoice() {
// ask the user for the species of Bird that they want
// return an object of that Bird species
}

void main() {
Bird b = getBirdFromUserChoice();
b.fly(); // this works perfectly fine!
}
}

```

```java
// new code - extension

class Kiwi extends Bird {


void fly() {
throw new UserIsAStupidPersonException("Dude, Kiwi's can't fly, even class 5th students know that.");
}
}

```

✅ Before extension
Code was working fine.
It was tested, it was performant, it was readable, it got shit done!

❌ After extension
I did not touch the old code - should the old code magically crash!? No.
I just added new code. But surprisingly, the OLD code crashes!

This violates expectations!

==================================
⭐ Liskov's Substitution Principle
==================================

- Any child `class C extends P` object should be able to replace any parent `class P` object without any
issues

- easier version: Subclasses should not violate expectations

- child classes can add new functionality, but they should not break existing functionality of the parent
class

🎨 Redesign the system

```java

abstract class Bird {


String species;
Float lengthOfBeak;

abstract void speak()

// abstract void fly() => not in this class - because not all birds can fly
}

interface ICanFly { // convention to start interfaces with I


void fly();
}

class Sparrow extends Bird implements ICanFly {


void speak() {...}
void fly() {...}
}

class Eagle extends Bird implements ICanFly {


void speak() {...}
void fly() {...}
}

class Kiwi extends Bird { // NOT implement the ICanFly interface


void speak() {...}
// we don't have to implement it - void fly() {...}
}

// inheritance is the way certain things behave


// You are the son of your dad - inheritance
// LSP is a guideline that child classes should follow
// You should not shout at your dad - principle that you as the child should follow

```

-----------------------------------------------------------------------------
Doesn't this violate open-close?
Earlier code was

```java
abstract class Bird {
abstract void fly();
}
```

Now we had to modify the close to be

```java
abstract class Bird {
String species;
Float lengthOfBeak;

abstract void speak()

// abstract void fly() => not in this class - because not all birds can fly
}

interface ICanFly { // convention to start interfaces with I


void fly();
}
```

-----------------------------------------------------------------------------

Software engineers at top comapnies get paid ridiculous salaries


This is exactly why

Your job is to anticipate / predict these future changes, and write the 2nd code from the very start

-----------------------------------------------------------------------------

✈️What else can fly?


=====================

```java

interface ICanFly {
void fly();

// when Birds fly - how do they do it?


// If I were a bird - push off my legs a little to get some lift, and I would spreak my wings
void kickToTakeOff();
void spreadWings();
}

class Balloon implements ICanFly {

class Shaktiman implements ICanFly {


void spreadWings() {
// Sorry shaktiman!
}
}
```

Bats, Aeroplane, Insects, Shaktiman, Kite (Patang), Drones, Imagination, Rumors, My mom chappal flies towards
me, Balloons

>
> ❓ 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
==================================

- Keep your interfaces minimal - no class should be forced to implement methods it doesn't need

- If you find a cluttered interface - you break it into multiple smaller interfaces

How will you fix `ICanFly`?

```java

interface ICanFly {
void fly();
}

interface ICanFlyLikeABird extends ICanFly {


// when Birds fly - how do they do it?
// If I were a bird - push off my legs a little to get some lift, and I would spreak my wings

void kickToTakeOff();
void spreadWings();
}

class Sparrow extends Bird implements ICanFlyLikeABird {...}


class Skatiman implements ICanFly {...}
```

🔗 Isn't this just the Single Responsibility Principle applied to interfaces?


Yes, of course!

"interfaces" are not just about class definitions - they are also about external interfaces - APIs

🔗 All the SOLID principles are linked together

Rules vs Guidelines
===================

- Rules
+ these are enforced by someone
+ don't kill - enforced by the police
+ wear your seatbelt - enforced by the police
+ pay your taxes - enforced by the government (unless you're a politician)
- Guidelines
+ good to have - suggestions - take it or leave it - best practices
+ don't smoke - you can smoke if you want - it will very unhealthy
+ wear a mask - you can survive without a mask as well, but you're more like to die to Covid
+ walk to your left - INCORRECT
+ drive to your left
+ walk to your right

------------------------
=> - - - - =>
------------------------
=> - - - - <=
------------------------

SOLID Principle - guidelines!

It is extremely important to follow them


Even more important - is to know when to break them!

You're a startup - you care about


- moving fast
- time to market
- product market fit
- understanding the customer
- building the MVP (minimal viable product)
- testing ideas quickly - failing fast
- care about funding
- you do NOT give any shits about the code quality
If you're Google, and have a project with 100,000 lines of code and 200 devs working on it
- you care about the design
- above everything else
- it is okay to postpone a feature by 2 weeks to ensure that the quality is maintained

-----------------------------------------------------------------------------

- we've designed the animals


- now let's design some structure(cages) to contain them

🗑️ Design a Cage
================

```java

// High level code => abstractions - it tells you what is there, but not how it works
// Low level code => implementation details - it tells you exactly how something works

interface IBowl { // high-level


void feed(Animal animal); // all interfaces are high-level
void clean();
boolean isEmpty();
void refill();
}

class MeatBowl implements IBowl {} // low-level


class GrainBowl implements IBowl {} // because this class must provide
class FruitBowl implements IBowl {} // the implementations for each of
// the functions defined in IBowl

interface IDoor { // high-level


void handleAttack(Attack attack);
boolean isSqueaky();
void open();
void close();
}

class WoodenDoor implements IDoor {} // low level


class IronDoor implements IDoor {}
class AdamantiumDoor implements IDoor {}

abstract class Animal { // high level


String species;

void eat(Food food);


}
class Bird extends Animal {} // low level
class Tiger extends Animal {} // low level
class Fish extends Animal {} // low level

class Cage1 { // high-level


// cage for Tigers // controller class
MeatBowl bowl = new MeatBowl();
IronDoor door = new IronDoor();
List<Tiger> occupants = new ArrayList<>(
new Tiger("kitty 1"),
new Tiger("kitty 2")
);

public Cage1() {}

void feed() { // Cage doesn't do the work itself


for(Tiger t: this.occupants) { // it delegates the work to
if(this.bowl.isEmpty()) // other classes
this.bowl.refill()
this.bowl.feed(t);
}
}

void handleAttack(Attack attack) {


this.door.handleAttack();
}
}

class Cage2 {
// cage for X-men
MeatBowl bowl = new MeatBowl();
AdamantiumDoor door = new AdamantiumDoor();
List<XMen> occupants = new ArrayList<>(
new Wolverine("kitty 1"),
new Deadpool("kitty 2")
);

public Cage2() {}

void feed() {
for(XMen t: this.occupants) {
if(this.bowl.isEmpty())
this.bowl.refill()
this.bowl.feed(t);
}
}

void handleAttack(Attack attack) {


this.door.handleAttack();
}
}

class MyAwesomeZooGame {
void main() {
Cage1 kittyCage = new Cage1();
kittyCage.feed();
kittyCage.handleAttack(new Attack(10));

Cage2 xmenCage = new Cage2();


// ...
}
}

```

🐞 Lot of code repetition

```
------- --------- -------
IBowl IAnimal IDoor high level
------- --------- -------
║ ║ ║
║ ║ ║
┏━━━━━━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━━┓
┃ MeatBowl ┃ ┃ Tiger ┃ ┃ IronDoor ┃ low level
┗━━━━━━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━━┛
│ │ │
╰───────────────╁───────────────╯

┏━━━━━━━┓
┃ Cage1 ┃ high level
┗━━━━━━━┛

```

High level class `Cage1` depends on low-level implementation details `MeatBowl`, ...

=================================
⭐ Dependency Inversion Principle
=================================

- High-level modules should NOT depend on low-level modules


- instead, high-level modules should only depend on high-level abstractions

```
------- --------- -------
IBowl IAnimal IDoor
------- --------- -------
│ │ │
╰───────────────╁──────────────╯

┏━━━━━━┓
┃ Cage ┃
┗━━━━━━┛
```

But how?

=======================
💉 Dependency Injection
=======================

- Inversion - principle
- Injection - how to achieve that

- Instead of creating your dependencies yourself, let your clients inject them.

```java

class Cage { // high-level


// Generic cage
IBowl bowl;
IDoor door;
List<Animal> occupants;

// injecting dependencies via the constructor


// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
public Cage(IBowl bowl, IDoor door, List<Animal> occupants) {
this.bowl = bowl;
this.door = door;
this.occupants = Arrays.asList(occupants);
}

void feed() { // Cage doesn't do the work itself


for(Tiger t: this.occupants) { // it delegates the work to
if(this.bowl.isEmpty()) // other classes
this.bowl.refill()
this.bowl.feed(t);
}
}

void handleAttack(Attack attack) {


this.door.handleAttack();
}
}

class MyAwesomeZooGame {
void main() {
Cage kittyCage = new Cage (
new MeatBowl(),
new IronDoor(),
Arrays.asList(
new Tiger(),
new Lion(),
)
);

Cage xmenCage = new Cage (


new MeatBowl(),
new AdamantiumDoor(),
Arrays.asList(...)
);

}
}

```

Enterprise Code
===============

Large companies like Google/Amazon

```java
class RazorPayPaymentGatewayBuilderFactory implement IPaymentGatewayBuilderFactory {
SimpleFileLogger logger = SimpleFileLogger.getInstance(RazorPayPaymentGatewayBuilderFactory.class);
}

```

- pattern everywhere
- strategies, factories, builders, factories of factories, factories of builders, builders of factories,
proxies, adapters, ...

- if you don't know LLD - even if you get into Google, you will have a hard time surviving there
- everytime you look at a piece of code, you would want to jump out of the window!
- if you know LLD, then you won't even have to read 95% of the code
- just looking at the class name will tell you exactly what the code does!

⏱️ Quick Recap
==============

================
🎁 Bonus Content
================

>
> We all need people who will give us feedback.
> That’s how we improve. 💬 Bill Gates
>

======================
⭐ Interview Questions
======================

> ❓
Which of the following is an example of breaking
> Dependency Inversion Principle?
>
> A) A high-level module that depends on a low-level module
> through an interface
>
> B) A high-level module that depends on a low-level module directly
>
> C) A low-level module that depends on a high-level module
> through an interface
>
> D) A low-level module that depends on a high-level module directly
>

> ❓ What is the main goal of the Interface Segregation Principle?


>
> A) To ensure that a class only needs to implement methods that are
> actually required by its client
>
> B) To ensure that a class can be reused without any issues
>
> C) To ensure that a class can be extended without modifying its source code
>
> D) To ensure that a class can be tested without any issues

>
> ❓ 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
>

=============================
⭐ How do we retain knowledge
=============================
>
> ❓ Do you ever feel like you know something but are unable to recall it?
>
> • Yes, happens all the time!
>
> • No. I'm a memory Jedi!
>

-------------
🧩 Assignment
-------------

https://github.com/kshitijmishra23/low-level-design-concepts/tree/master/src/oops/SOLID/

# ============================ That's all, folks! ===========================

You might also like