SOLID Principles
SOLID Principles
=======================
Pragy
Senior Software Engineer + Instructor @ Scaler
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 any devs work time is spent writing actual code!
Counterintuitive
- planning
   - designing
   - reading other people's code
   - meetings
   - scrum
   - architecture design
   - researching - docs, articles, ..
   - requirements gathering
- debugging
- testing
- documentation
- performance reports
- code reviews
✅  Goals
========
1.    readable
2.    testable
3.    maintainable
4.    extensible
===================
💎  SOLID Principles
===================
-    Single Responsibility
-    Open/Close
-    Liskov's Substitution
-    Interface Segregation
-    Dependency Inversion
💭  Context
==========
- Zoo Game   🦊
- characters - animals, zoo staff, visitors
- structures - cages, park, ponds, ..
These concepts will be applicable to any modern language that supports OOP
Python/Java/JS/Typescript/C++/Ruby/C#/Php ..
-----------------------------------------------------------------------------
🎨   Design a Character
======================
```java
class ZooEntity {
   // a character inside our zoo game - concept in our mind
    // properties (attributes)
       // animal
       Integer id;
       String name;
       Integer age;
       Integer weight;
       Boolean isNonVegetarian;
       Color color;
       String species;
       Gender gender;
      // zoo staff
      Integer employeeID;
      String name;
      Integer age;
      Gender gender;
      String department;
      // visitor
      Integer ticketID;
      String name;
      String phone;
      String address;
      Boolean isVIP;
      Gender gender;
    // methods (behavior)
       // animal
       void eat();
       void poop();
        void   sleep();
        void   attack();
        void   speak();
        void   run();
        void   fly();
        // zoo staff
        void eat();
        void poop();
        void speak();
        void feedAnimals();
        void checkIn();
        // visitor
        void eat();
        void poop();
        void getEatenByALion();
        void takePhoto();
        void speak();
```
Major Issues
1. name collisions for attributes / methods - easy fix - just rename
❓  Readable
Can I read it and understand it? Yes, certainly.
Consider a project with 100,000+ lines of code, 100 devs working on it, 5 year long
project.
Readable right now, but as complexity grows, it will quickly become unreadable.
❓  Testable
I can totally write testcases for each method.
However, because all the code is in a single class, changing the behavior of animal
can (by mistake) end up changing the behavior of the Visitor
❓  Extensible
(we will come back to this later)
❓  Maintainable
dev 1 - features for animals   - editing class Zooentity
dev 2 - features for visitors - editing class Zooentity
submit code changes - merge conflicts
==================================
⭐  Single Responsibility Principle
==================================
- if you identify that a piece of code has multiple responsibilities - split the
code into multiple units
```java
// OOP - class Inheritance
class ZooEntity {
   // common   - age, gender, id, eating, pooping
      Integer id;
      String name;
      Integer age;
      Gender gender;
      void eat();
      void poop();
      void speak();
}
      void attack();
      void run();
      void eatAVisitor();
}
      void feedAnimals();
      void checkIn();
      void cleanPremises();
}
      /// ...
}
```
- Readable
aren't there wayy too many classes/files now?
      yes! earlier we had 1 class. Now we have 4 classes
      but that's not an issue!
         - because you will never work on multiple features at once.
         - at any given time you have to read only 1 or a handful of classes/files
   yes we have multiple classes now, but each class is now small and very easy to
understand!
- Testable
Can a change in Animal class effect the behavior of the Staff class?
No!
Responsibilites are now decoupled!
- Maintainable
if multiple devs are working on different files, will it cause merge conflicts?
No / or at least minimized
-----------------------------------------------------------------------------
🐦  Design a Bird
================
```java
      // void chirp();
      // void poopOnPeopleBelow();
      void fly();
}
```
```java
class Bird {
   // String species; // inherited from parent class
      void fly() {
         // how can we implement this?
          if (species == "Sparrow")
             print("fly low")
          else if (species == "eagle")
             print("glide elegantly high up above")
          else if (species == "pigeon")
             print("poop attack anyone below you and fly")
      }
}
```
- Readable
- Testable
- Maintainable
- Extensible - FOCUS!
Imagine that I need to add a new Bird type - what code changes will I need to make?
```java
[PublicZooLibary]
{
   class Bird {
      // String species; // inherited from parent class
          void fly() {
             // how can we implement this?
              if (species == "Sparrow")
                 print("fly low")
              else if (species == "eagle")
                 print("glide elegantly high up above")
              else if (species == "pigeon")
                 print("poop attack anyone below you and fly")
/**
              */
          }
      }
}
[MyCustomGame] {
import PublicZooLibary.Bird;
      class ZooGame {
         void main() {
            Bird sparrow = new Bird(...);
            sparrow.fly();
```
To add a new type of Bird, I will have to modify the Bird class
- Is it always the case that you have modification access to the source code of
everything that you use?
   - Do you always write everything from scratch?
      No - you use external libraries
   - A lot of libraries are shipped in compiled formats (.dll .com .exe .so .jar
.pyc)
=======================
⭐  Open/Close Principle
=======================
- Code should be closed for modification, yet, it should remain open for extension!
   - modification: changing existing code
      - you might have to change it for bug fixes
      - but you should not change existing code for changing requirements
   - extension: adding new functionality in the face of changing requirements
Seems impossible! How can we add new functionality without modifying existing code?
[PublicZooLibary]
{
   abstract class Bird {
      // String species; // inherited from parent class
[MyCustomGame] {
      import    PublicZooLibary.Bird;
      import    PublicZooLibary.Sparrow;
      import    PublicZooLibary.Eagle;
      import    PublicZooLibary.Pigeon;
      class ZooGame {
         void main() {
            Sparrow sparrow = new Sparrow(...);
            sparrow.fly();
```
- Extension
We were able to extend the Bird functionality (added a new type - Peacock) without
modifying existing code
❔  Isn't this the same thing that we did for Single Responsibility as well?
yes - for SRP we split the class by using inhertiance
      for OCP we split the class by using inheritance
❔  Does that mean that OCP == SRP?
No - the solution was the same (OOP) but the intent was different
intent for SRP - was split the responsibilities - readable, testabile, maintainable
intent for OCP - to make the code extensible despite it not being modifiable
🔗  All the SOLID principles are tightly linked together - adhereing to 1 will
automatically make your code good for some others.
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
quick 15 mins break:
resuming at 9.15 PM sharp
-----------------------------------------------------------------------------
- Single Responsibility
- Open/Closed
```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 your problems - simply not implement the `void fly()`
```java
```java
```
```java
class ZooGame {
   Bird getBirdObjectFromUserChoice() {
      // it shows multiple bird types to the user
      // it lets the user select a species
      // creates a Bird object of that species
      // returns that object
      // this function can return a Bird object of type Sparrow, or a Bird object of
type Pigeon
      // Runtime polymorphism:
         // Sparrow s = new Sparrow();
         // return s; // this is allowed, because every sparrow is also a Bird
   }
      void main() {
         Bird b = getBirdObjectFromUserChoice();
         b.fly();
      }
}
```
✅  Before extension
Code works, easy to understand, passes testcases, everyone is happy
(dev happy, user happy, QA happy)
❌   After extension
```java
class Kiwi extends Bird {
    void fly() {
       throw new FlightlessBirdException("kiwi's don't fly")
    }
}
```
==================================
⭐  Liskov's Substitution Principle
==================================
- Child classes should not violate expectations set by the parent class
- Child classes should not be forced to implement behavior that they can't exhibit
(don't force all Birds to fly, because some of them can't)
```java
class Bird {
   // usual stuff
   void poop(); // all birds do poop
   void eat();    // all birds do eat
   // Note: because some birds cannot fly, it will be wrong to expect this from the
Bird class
}
interface ICanFly {
   void fly();
}
// flying birds
class Sparrow extends Bird implements ICanFly {
   void fly() { print("fly low") }
}
class Pigeon extends Bird implements ICanFly {
   void fly() { print("poop attack anyone below you and fly") }
}
// flightless birds
class Kiwi extends Bird {
   // No need to implement ICanFly Interface
}
class ZooGame {
   ICanFly getFlyingObjectFromUserChoice() {
      // it shows multiple bird types to the user
      // it lets the user select a species
      // creates a Bird object of that species
      // returns that object
      // this function can return a Bird object of type Sparrow, or a Bird object of
type Pigeon
      // Runtime polymorphism:
         // Sparrow s = new Sparrow();
         // return s; // this is allowed, because every sparrow is also a ICanFly
   }
      void main() {
         ICanFly b = getFlyingObjectFromUserChoice();
         b.fly(); // this is perfectly fine!
      }
}
```
Q: but Pragy, didn't we modify existing code for this change to happen?
Q: aren't we violating the OCP?
Yes, it would be wrong to start with a bad design and then refactor it into good
design - because then you would be modifying existing code
You want to design the system in a good way right from the very start!
-----------------------------------------------------------------------------
```java
class Bird {
   // Note: because some birds cannot fly, it will be wrong to expect this from the
Bird class
}
interface ICanFly {
   void fly();
      void flapWings();
}
      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
==================================
note: client =/= user. client == any code that uses your interface
❓ Isn't this similar to LSP? Isn't this just SRP applied to interfaces?
Yes & yes.
But intent is different
Liskov's Substition - type hierarchy (mathematics of the type system in code)
Interface Segregation - writing good code
-----------------------------------------------------------------------------
🗑️ Design a Cage
================
```java
interface IDoor {
   void resistAttack(Attack attack);      // High Level Abstraction
}
class IronDoor implements IDoor {         // Low Level Implementation Detail
   void resistAttack(Attack attack) {
      if(attack.power <= IRON_MAX_RESISTANCE)
         return;
      print("Door Broken down - all animals are now dead/escaped")
   }
}
class WoodenDoor implements IDoor { ... } // Low Level Implementation Detail
class AdamantiumDoor implements IDoor { ... } // Low Level ...
     void feed() {
        for(Tiger t: kitties) {
           this.bowl.feed(t);   // delegating the feeding task to a dependency
        }
     }
class Cage2 {
   // building a cage for chickens
      // dependencies - Bowls, Animals, Doors, ...
      void feed() {
         for(Chicken c: chicks) {
            this.bowl.feed(c);   // delegating the feeding task to a dependency
         }
      }
      // ...
}
class MyAwesomeZooGame {
   void main() {
      Cage1 forTigers = new Cage1();
      forTigers.feed();
          CageXMen ...
      }
}
```
High Level
   - abstractions: a piece of code that tells you what to do, but not how to do it
(interfaces / abstract classes)
   - controllers: manegerial code that delegates tasks to dependencies
Low Level
   - implementation details: tell you exactly how something is being done!
```
```
In the above code, the High Level `Cage1` class depends on Low level implementation
details `MeatBowl`, `Tiger`, `IronDoor`
=================================
⭐  Dependency Inversion Principle                  - what to do
=================================
```
      -------        ---------       -------
       IBowl           Animal         IDoor            high level abstractions
      -------        ---------       -------
         │               │              │
         ╰───────────────╁──────────────╯
                         ┃
                      ┏━━━━━━┓
                      ┃ Cage ┃                          code
                      ┗━━━━━━┛
```
But how?
=======================
💉  Dependency Injection                           - how to achieve it
=======================
- Don't create the dependencies yourself - let your clients supply (inject) the
dependencies into you (via constructor/function params)
```java
      IBowl bowl;
      IDoor door;
      List<Animal> animals;
      void feed() {
         for(Animal a: animals) {
            this.bowl.feed(a);   // delegating the feeding task to a dependency
         }
      }
class MyAwesomeZooGame {
   void main() {
      Cage forTigers = new Cage(
         new MeatBowl(),
         new IronDoor(),
         Arrays.asList(new Tiger("simba"), new Tiger("mufasa")),
      );
      forTigers.feed();
Enterprise Code
===============
When you go to large companies like Google
    - you will find "overengineered code"
That's okay
   - because large companies have projects that have
      - 100,000+ LOC
      - 100s of devs
      - 10+ years in pipeline
      - devs join & leave all the time
      - requirements change frequently
      - huge userbases, any mistakes can effect revenue or customer loyalty
Always predict any and all future requirements, and design code from day 1 so that
you don't have to modify it later!
================
🎁  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
>