This repository provides examples illustrating the implementation of each SOLID principle (Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle) in web development. Each example showcases the application of a specific principle before and after its implementation.
For each SOLID principle, we provide before and after examples to demonstrate how adherence to these principles can lead to improved code quality, maintainability, and scalability.
Each class or module should have only one reason to change, meaning it should have only one responsibility.
- Before: Go to example
- After: Go to example
The code demonstrates SRP by separating concerns related to users and email functionalities into distinct classes.
- Defines a user entity with properties for name and email.
- Contains methods for saving a user and sending an email.
- Ensures that the
User
class has a single responsibility, managing user-related operations.
- Similar to the
User
class but without the methods for saving a user and sending an email. - Maintains a single responsibility of representing user data without additional functionalities.
- Responsible for saving user data to a repository.
- Demonstrates SRP by handling user persistence without any email-related functionality.
- Handles the responsibility of sending emails.
- Separate from user-related concerns, adhering to SRP by focusing solely on email functionality.
By segregating functionalities into separate classes based on their responsibilities, the code follows the SRP, making it easier to maintain and extend.
Software entities should be open for extension but closed for modification, allowing for new functionality to be added without altering existing code.
- Before: Go to example
- After: Go to example
The code showcases the OCP by designing classes that are open for extension but closed for modification, allowing easy addition of new shapes without altering existing code.
- Defines classes for
Rectangle
andTriangle
, each with a specific calculation method for its area. AreaCalculator
class calculates the total area based on the provided shapes, but it requires modification whenever a new shape is introduced, violating the OCP.
- Introduces the
Shape
interface representing a common behavior for all shapes - calculating area. - Classes like
Rectangle2
andCircle
implement theShape
interface, providing their specific area calculation methods. AreaCalculator2
accepts an array ofShape
instances, adhering to the OCP by being open to new shapes without needing modifications, thus promoting code extensibility.
By adopting the OCP, the codebase becomes more flexible, allowing for the addition of new shapes without the need for changes in existing classes, enhancing maintainability and scalability.
Subtypes should be substitutable for their base types without altering the correctness of the program, ensuring that derived classes can be used interchangeably with their base classes.
- Before: Go to example
- After: Go to example
The code demonstrates adherence to the Liskov Substitution Principle by ensuring that derived classes can be substituted for their base classes without affecting the program's correctness.
- Defines a base class
Bird
with methodseat()
andfly()
. - Both
Duck
andOstrich
extend fromBird
, inheriting its behavior. - Instances of
Duck
andOstrich
can be treated as instances ofBird
, showcasing LSP compliance. - While both can eat, only ducks can fly, which results in a runtime error when trying to make an ostrich fly, highlighting a violation of the LSP.
- Introduces classes
Birds
andFlyingBirds
representing different categories of birds. Sparrow
andChicken
extend fromBirds
, withSparrow
being a flying bird.Sparrow
can be substituted for its base classFlyingBirds
without issues, ensuring LSP adherence.- Similarly,
Chicken
can be treated as an instance ofBirds
without any problems.
By ensuring that subclasses can be used interchangeably with their base classes without altering the program's behavior, the code conforms to the Liskov Substitution Principle, promoting modularity and extensibility.
Clients should not be forced to depend on interfaces they don't use. Instead of large interfaces, use smaller, more specific ones tailored to each client's needs.
- Before: Go to example
- After: Go to example
The code exemplifies the Interface Segregation Principle by ensuring that clients are not forced to depend on interfaces they don't use, and by preferring smaller, more specific interfaces tailored to each client's needs.
- Defines an interface
WorkerAtFactory
with methodswork()
andeat()
, representing workers at a factory. - Classes like
FactoryWorker
,Manager
, andRobot
implement this interface, but theRobot
class cannot correctly implement theeat()
method, violating the ISP.
- Introduces smaller and more specific interfaces
WorkerAtFactories
andEater
to avoid unnecessary dependencies. - Classes like
FactoryWorker2
andManager2
implement only the methods they need, adhering to the ISP. - The
Robot2
class, which doesn't need to eat, doesn't implement theEater
interface, ensuring that it adheres to the ISP by not being forced to implement unnecessary methods.
By adhering to the ISP, the codebase becomes more modular and maintainable, as interfaces are tailored to specific client requirements, reducing unnecessary dependencies and ensuring cleaner code design.
High-level modules should not depend on low-level modules, but both should depend on abstractions. Abstractions should not depend on details; rather, details should depend on abstractions.
- Before: Go to example
- After: Go to example
The code showcases the Interface Segregation Principle by ensuring that clients are not forced to depend on interfaces they don't use, and by preferring smaller, more specific interfaces tailored to each client's needs.
- Defines classes
FrontEndDeveloper2
andBackEndDeveloper2
, each with specific methods for writing code in JavaScript and C# respectively. Project2
class depends directly onFrontEndDeveloper2
, violating the ISP as it's tightly coupled to a specific implementation of a developer.
- Introduces a more modular approach with interfaces.
- Defines
Developer
interface with a single methodwriteCode()
. - Both
FrontEndDeveloper
andBackEndDeveloper
implement theDeveloper
interface, ensuring adherence to the ISP by providing a common interface for writing code. Project
class now depends on theDeveloper
interface rather than concrete implementations, promoting loose coupling and adhering to the ISP.
By adhering to the ISP, the code becomes more modular and maintainable, as interfaces are tailored to specific client requirements, reducing unnecessary dependencies and ensuring cleaner code design.
Contributions are welcome! If you have any suggestions, ideas, or want to report issues, feel free to open an issue or submit a pull request.
This project is licensed under the MIT License.