System Design in UI
August 18th 2024 | 3 min read
143 | 7

Part 1 — SOLID
Hello Everyone,
Welcome back to my blog 🙏.
I hope you are all safe and that this blog finds you in good health ️🫶.
Single Responsibility Principle (SRP)
Every Component should have one and one responsibility and also it should be one reason to change as well.
- Cohesion — The degree to which various parts of components are related.
- Coupling — The level of interdependency of the components.
Always aim for high Cohesion and loose Coupling.
Following SRP will make your software more maintainable
Let’s understand with an example:
// name.utils.js
// Low Coupling
const firstName = () => "Mahendra"
// Low Coupling
const middleName = () => "Singh"
// Low Coupling
const lastName = () => "Dhoni"
export default {
firstName,
middleName,
lastName,
}
If you have observed the above example, You see multiple functions are closely relative to one thing (i.e. name). So, this utils file is highly cohesive.
Functions inside this file perform Single Task and without any dependency. Hence it has Low Coupling.
Now let’s see the High coupling example:
const fullName = () => firstName() + middleName() + lastName()
Open-Closed Principle (OCP)
- Open for Extension
- Closed for Modification
Benefits
- Ease of Adding new features
- Minimal cost of developing and testing
- The open-closed principle tries to decouple things and make things simpler
Example: Interfaces — implements
// discount.js
const calculateDiscount = (voucher) => {
return {
'NEW': '50%',
'NEW_YEAR': '25%',
'DUSSEHRA': '45%'
}[voucher]
}
If you have observed the above example calculateDiscount
is a straightforward function that can be extendable with more vouchers in the future and really there is nothing to be modified in this.
This is a more functional way but, there are many examples with respect to class-based approaches such as Interfaces.
Liskov — Substitution Principle (LSP)
Objects should be replaceable with their subtypes without affecting the correctness of the program.
- Breaking the Hierarchy Pattern
- Tell, Don’t ask
const calculateCircleArea = (radius) => {
return Math.PI * radius * radius;
}
const calculateRectangleArea = (width, height) => {
return width * height;
}
const circle = { type: "circle", radius: 5 };
const rectangle = { type: "rectangle", width: 4, height: 6 };
const calculateArea = (shape) => {
switch (shape.type) {
case "circle":
return calculateCircleArea(shape.radius);
case "rectangle":
return calculateRectangleArea(shape.width, shape.height);
default:
throw new Error("Unknown shape type");
}
}
console.log(calculateArea(circle)); // Output: 78.53981633974483
console.log(calculateArea(rectangle)); // Output: 24
In this example, we break the hierarchy pattern by avoiding class inheritance. Instead, we use pure functions to calculate the area of specific shapes (calculateCircleArea
and calculateRectangleArea
). The calculateArea
function takes a shape object as input and calculates its area based on its type. This approach uses composition and function calls instead of class hierarchies to achieve the same result.
Interface Segregation Principle (ISP)
No client should be forced to depend on methods it does not use.
Do not create generic methods so that they should be forcibly used even if unnecessary.
Techniques to Find Violations in ISP
- Fat Interfaces
- Interface with lower cohesion
- Empty method implementations
const actions = {
fly: () => {
console.log('walking');
},
run: () => {
console.log('running');
}
}
const bird = {
...actions
}
const dog = {
...actions
}
console.log(bird.fly()) // this is correct.
console.log(bird.run()) // this is wrong.
console.log(dog.fly()) // this is wrong.
console.log(dog.run()) // this is correct.
If you have observed the above functions. A bird can fly but can’t swim and vice-versa. As it is part of actions
the object it was forcefully added to the dog and bird objects. so, this violates the ISP.
How to correct it?
const fly = () => {
console.log('flying');
};
const run = () => {
console.log('running');
}
const bird = {
fly
}
const dog = {
run
}
console.log(bird.fly()) // flying.
console.log(bird.run()) // error
console.log(dog.run()) // running
console.log(dog.fly()) // error.
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules, Both should depend on abstractions.
Abstractions should be independent of details. Details should depend on abstractions.
- Dependency Injection Vs Dependency Inversion
- Inversion of Control (Spring Framework)
// Abstraction (interface)
function DatabaseSaveFunction(data) {}
// Concrete implementation
function saveToMySQL(data) {
// Implementation details for MySQL
}
// Concrete implementation
function saveToMongoDB(data) {
// Implementation details for MongoDB
}
// DatabaseSaveFunction
function saveData(data, saveFunction) {
return saveFunction(data);
}
const data = { /* some data */ };
// Using the MySQL implementation
const resultMySQL = saveData(data, saveToMySQL);
// Using the MongoDB implementation
const resultMongoDB = saveData(data, saveToMongoDB);
Conclusion
SOLID Principles are interlinked with each other and work effectively if combined.
They complement each other and work together, to achieve a common purpose of well-designed software.
Here we go, That’s it folks for this short blog.
I hope everyone liked this blog.
If Yes, please share it with your friend.
For more exciting frontend content please follow me.
Let’s Learn, Explore and Excel together :)
Thanks a lot, Everyone 🙏.
Until Next time,
Happy Learning 📖✍️.
Abhishek Kovuri, UI developer