SOLID Principles
SOLID Principles
================
❓ 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
Pragy
Senior Software Engineer + Instructor @ Scaler
https://linktr.ee/agarwal.pragy
Important Points
================
>
> ❓ What % of your work time is spend writing new code?
>
> • 10-15% • 15-40% • 40-80% • > 80%
>
- learning
- brainstorming
- reading code / docs
- maintainable
- KT
- meetings
✅ Goals
========
1. Readability
2. Extensibility
3. Maintainability
4. Testability
===================
💎 SOLID Principles
===================
- Single Responsibility
- Open/Close
- Liskov's Substitution
- Interface Segregation
- Dependency Inversion
Interface Segregation / Inversion of Control
Dependency Inversion / Dependency Injection
This class will follow "java-like syntax" - not exactly java - it will be pseudo-
code
Any principles that we discuss here will be applicable in any programming language
that supports Object Oriented Programming
💭 Context
==========
- Zoo game 🦊
- characters - Animals, Staff, Visitors
- structures - Gardens, Roads & Paths, Cages
-----------------------------------------------------------------------------
```java
/**
* Concept (in mind) - Class (in code)
* Attributes (in mind) - properties (in code)
* Behavior (in mind) - methods (in code)
*/
class ZooEntity {
// it abstracts all the various characters present in the zoo
// properties [attributes]
// zoo staff
String name; // rename - staffName
Integer age;
Integer employeeId;
String phoneNumber;
String address;
// visitors
String name; // rename - visitorName
Integer age;
Ticket ticket;
String phoneNumber;
// animals
String species;
String name;
Integer age;
Integer weight;
Boolean canFly;
// methods [behavior]
// staff
void clean() { ... }
void eat() { ... }
void poop() { ... }
void sleep() { ... }
void feedAnimals() { ... }
// visitor
void eat() { ... }
void poop() { ... }
void feedAnimals() { ... }
void getEatenByLion() { ... }
// animals
void eat() { ... }
void poop() { ... }
void sleep() { ... }
void attackVisitors() { ... }
```
❓ Readable
As the codebase becomes larger and the features grow, this will quickly get out of
hands!
❓ Testable
❓ Extensible
will come back to this
❓ Maintainable
what happens when instead of 1 dev you have 100 devs on the team?
Because everyone is working on the same class, then they finally commit the code -
we will get merge conflicts!
```java
```
- Readable
Now we have too many classes (earlier - 1, now - 4)
Lots of boilerplate `class X extends Y`
At any given time, you will have to work with 1 (or maybe a handful) of features - a
handful of classes
Each class is now very simple to understand - small, and it makes sense!
- Testable
If I make changes in the Animal class, will that effect the behavior of the Visitor
class?
No!
Testcases will now be more robust! Because the code is decoupled!
- Maintainable
If diff devs are working on different classes - what happens to the merge conflicts
Goes down!
-----------------------------------------------------------------------------
Prerequisite - Object Oriented Programming
-----------------------------------------------------------------------------
🐦 Design a Bird
================
```java
class Bird extends Animal {
void fly() {
// Different birds fly differently!
if (species == "sparrow")
print("fly low")
else if (species == "eagle")
print("glide high in the sky")
else if (species == "peacock")
print("females can fly, males cant")
}
}
```
- Readable
- Testable
- Maintainable
- Extensible - FOCUS!
Do we write all our code from scratch? Or do we sometimes import external libraries?
```java
void fly() {
// Different birds fly differently!
if (species == "sparrow")
print("fly low")
else if (species == "eagle")
print("glide high in the sky")
else if (species == "peacock")
print("females can fly, males cant")
// I canNOT!
class App {
void main() {
Bird sparrow = new Bird(...);
sparrow.fly();
=======================
⭐ Open/Close Principle
=======================
Seems impossible! how can I extend (add functionality) to code, without modifying
it?
Don't modify "existing" code which is already working
If some piece of code has already gone through all this crap, then you don't wanna
repeat it.
```java
class App {
void main() {
Sparrow sparrow = new Sparrow(...);
sparrow.fly();
```
- Extension
Did we modify existing code?
No! We did not even have the source code
But, did we add new functionlaity?
Yes!
As devs, we should write our code in a manner that allows others to extend it
without modifying it.
Why would the comapnies pay so much - what do these devs do differently?
These people are expected to anticipate future requirement changes, and write code
that is extensible from day 1.
(design patterns)
❔ Isn't this the same thing that we did for Single Responsibility as well?
In both cases, we simply broke the large class down into smaller classes
🔗 All the SOLID principles are tightly linked together. Adhering to one might give
you others for free!
-----------------------------------------------------------------------------
Projects
High Level Design
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
```java
class Eagle extends Bird { void fly() { print("glide high in the sky") } }
```
>
> ❓ How do we solve this?
>
> • Throw exception with a proper message
> • Don't implement the `fly()` method
> • Return `null`
> • Redesign the system
>
```java
```
🐞 Compiler Error!
Anyone who inherits from this class, either they should provide the void fly, or
they should themselves be marked abstract
```java
class Eagle extends Bird { void fly() { print("glide high in the sky") } }
```
🐞 Violates expectations!
```java
class Eagle extends Bird { void fly() { print("glide high in the sky") } }
class ZooGame {
Bird getBirdFromUserChoice() {
// shows all the various bird species to user
// user chooses one
// creates an object for that species
// returns that object
if(choice == "sparrow")
return new Sparrow("tweety")
}
void main() {
Bird birdie = getBirdFromUserChoice();
birdie.fly();
}
```
✅ Before extension
- code is working fine
- well tested
- working
- dev is happy, and the user is happy
❌ After extension
==================================
⭐ Liskov's Substitution Principle
==================================
- any place where you expect an object of `class Parent`, you should be able to use
an object of `class Child extends Parent` without running into any issues!
- any child class should support all functionality of the parent class
- it can add additional functionality of its own
- but it should support at least whatever the parent supports
- otherwise, you need to redesign
```java
interface ICanFly {
void fly();
}
class Sparrow extends Bird implements ICanFly { void fly() { print("fly low") }}
class Eagle extends Bird implements ICanFly { void fly() { print("glide high in the
sky") } }
class ZooGame {
ICanFly getBirdFromUserChoice() {
// shows all the various bird species to user
// user chooses one
// creates an object for that species
// returns that object
if(choice == "sparrow")
return new Sparrow("tweety")
}
void main() {
ICanFly birdie = getBirdFromUserChoice();
birdie.fly(); // this will NEVER violate expectations!
}
}
```
```py
class Bird(ABC):
def chirp(self):
...
class ICanFly(ABC):
@abstractmethod
def fly(self):
raise NotImplementedError
class Kiwi(Bird):
...
# no need of fly() method here
```
-----------------------------------------------------------------------------
```java
interface ICanFly {
void flapWings();
void kickToTakeOff();
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]
>
No. They should not be part of ICanFly, if it is possible for things that can fly,
but don't have wings!
==================================
⭐ Interface Segregation Principle
==================================
- your client (another piece of code that is using your class/interface) should not
be forced to implement functionality that it does not support
```java
abstract class Bird {}
interface ICanFly {
void fly()
}
interface IHasWings {
void flapWings()
}
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
🗑️ Design a Cage
================
```java
/*
High Level
abstractions
code that doesn't tell you how to do things
just tells you what needs to be done
controllers
code that delegates the task forward to low level implementation details
acts as manager
Low Level
implementation details
code that tell you exactly how to perform certain task
*/
public Cage1 {
this.tigers.add(new Tiger(...));
this.tigers.add(new Tiger(...));
}
void feed() {
for(Tiger t: this.tigers)
this.bowl.feed(t) // delegate the task to the bowl
}
public Cage2 {
this.birds.add(new Sparrow(...));
this.birds.add(new Pigeon(...));
}
void feed() {
for(Bird t: this.birds)
this.bowl.feed(t) // delegate the task to the bowl
}
class Cage3 {
// create a cage for X-men
public Cage3 {
this.xmen.add(new Wolverine(...));
this.xmen.add(new Cyclops(...));
}
void feed() {
for(Animal t: this.xmen)
this.bowl.feed(t) // delegate the task to the bowl
}
class ZooGame {
void main() {
Cage1 tigerCage = new Cage1();
tigerCage.feed();
}
}
```
```
------- --------- -------
IBowl Animal IDoor high level abstractions
------- --------- -------
║ ║ ║
║ ║ ║
┏━━━━━━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━━┓
┃ MeatBowl ┃ ┃ Tiger ┃ ┃ IronDoor ┃ low level implementation details
┗━━━━━━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━━┛
│ │ │
╰───────────────╁───────────────╯
┃
┏━━━━━━━┓
┃ Cage1 ┃ high level code
┗━━━━━━━┛
```
In the above code, the high level code `Cage1` depends on the low level
implementation details `MeatBowl`, `Tiger`, `IronDoor`
=================================
⭐ Dependency Inversion Principle - what to achieve
=================================
```
------- --------- -------
IBowl Animal IDoor high level abstractions
------- --------- -------
│ │ │
╰───────────────╁──────────────╯
┃
┏━━━━━━┓
┃ Cage ┃
┗━━━━━━┛
```
But how?
=======================
💉 Dependency Injection - how to achieve it
=======================
```java
IBowl bowl;
IDoor door;
List<Animal> occupants = new ArrayList();
void feed() {
for(Animal t: this.occupants)
this.bowl.feed(t) // delegate the task to the bowl
}
class ZooGame {
void main() {
Cage tigerCage = new Cage(
new MeatBowl(...),
new IronDoor(...),
Arrays.asList(new Tiger(...), new Lion(...))
);
tigerCage.feed();
}
}
```
If I do this, my code is smaller (got rid of 2 classes), yet, my client has more
power!
Enterprise Code
===============
When you go to companies like Google - you will see extremely over engineered code!
- long names, various design patterns, all sort of weird things going around
================
🎁 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
>