link: Design Principles
SOLID Principles
Overview
The SOLID principles are like a set of rules for software developers. They help make sure that the programs they create are easy to work with, can grow without too much trouble, and are clear to understand.
Abstract
Think of SOLID as a toolbox for building software. These principles are the foundation for creating strong and reliable systems in object-oriented programming.
Content
Key Concepts
Each SOLID principle focuses on a different aspect of how to design software, working together to create a codebase thatâs organized and adaptable:
Important
- SRP (Single Responsibility Principle): This says that each class should do just one job.
- OCP (Open Closed Principle): It suggests designing modules in a way that you can add new features without changing the existing code.
- LSP (Liskov Substitution Principle) :It ensures that you can use a subclass wherever you would use the superclass without breaking the program.
- ISP (Interface Segregation Principle): This principle encourages having specific interfaces for each client rather than one big interface for everyone.
- DIP (Dependency Inversion Principle): It advises relying on abstract concepts rather than specific implementations.
SRP
Single Responsibility Principle
Overview
A class should have just one reason to change.
The Single Responsibility Principle (SRP) is a basic rule in object-oriented programming that ensures each class in a program handles just one part of the softwareâs functionality. According to SRP, a class should only have one reason to change, which means it should only have one job.
Abstract
Applying SRP makes classes simpler to manage, test, and maintain. It focuses on giving each class one task to perform well, which reduces coding mistakes and improves the quality of the software. This principle helps keep the program organized and makes it easier to update as needs change.
Content
Principle Explained
The SRP encourages developers to design classes that are dedicated to one functionality:
Important
- Focused Classes: Each class should handle a single functionality, avoiding the mixture of multiple tasks that complicate both the classâs purpose and its future modifications.
- Minimized Impact of Changes: Isolating responsibilities ensures that changes made to address one concern have minimal effects on other aspects of the program, fostering a more robust and less error-prone development environment.
Warning
if a class does too many things, you have to change it every time one of these things changes. While doing that, youâre risking breaking other parts of the class which you didnât even intend to change.
Practical Example
Refactoring a class to adhere to SRP involves:
- Identifying Multiple Behaviors: Recognize when a class is handling more than one responsibility.
- Separating Concerns: Create new classes to handle each responsibility separately.
BEFORE
BEFORE: A class with mixed responsibilities is harder to maintain.
Link to originalAFTER
AFTER: Each responsibility is handled by a dedicated class, leading to a cleaner design.
OCP
Open Closed Principle
Overview
Classes should be open for extension but closed for modification.
The Open/Closed Principle (OCP) is a fundamental rule in object-oriented programming. It encourages classes and other software parts to be extendable without needing to change the existing code. This principle is all about making a system strong and easy to maintain. It means you can add new features without causing problems in whatâs already there.
Abstract
Following the OCP prevents problems in existing features when you add new ones. It makes updating and expanding the software simpler. Essentially, you can add new features by creating new classes that build upon the existing ones, without having to touch the existing code.
Content
Principle Explained
The OCP promotes designing classes that are both open to extension and closed to modifications:
Important
- Extendable: You can extend the functionalities of a class through subclasses.
- Non-modifiable: Once completed, the original class should not need to be modified to add new features.
Practical Application
When you follow the Open Closed Principle (OCP), you usually use interfaces or abstract classes. This helps create a codebase that can easily grow without having to change whatâs already there. By doing this, you keep your system intact and ready to adjust to new needs or changes.
Practical Example
Consider a banking application with various account types each having different rules and interest calculations.
Initial Implementation: The initial class design directly incorporates multiple account types, leading to a rigid and difficult-to-modify structure.
csharp
Refactored Implementation: By abstracting common functionalities into an interface and implementing specific behaviors in separate classes, we adhere to OCP.
csharp
This approach allows each account type to maintain its unique implementation while adhering to a common interface, facilitating easy expansions or modifications without altering the existing codebase.
Above code is implementing both OCP and SRP principle, as each class is doing a single task and we are not modifying class and only doing an extension.
Summary
Link to originalSummary
The Open/Closed Principle is instrumental in developing software that is capable of growing and changing with minimal disturbance to existing functionality. By designing software systems that are open for extension but closed for modification, developers can add new features with ease while maintaining stability in the existing code.
LSP
Liskov Substitution Principle
Overview
Objects of a subclass should be replaceable with objects of their superclass without altering the correctness of the program.
The Liskov Substitution Principle (LSP) is an essential rule in object-oriented programming. It promotes the idea that subclasses should be able to replace their parent classes without causing any issues. This principle guarantees that classes built from a base class can be used just like the base class itself, without users needing to worry about any differences.
Abstract
By following LSP, software becomes more reliable and easier to work with. It ensures that new classes built from existing ones donât change how the original classes behave, making the system easier to maintain and expand.
Content
Principle Explained
LSP is about ensuring that derived classes only extend without replacing the functionality of their base classes:
Important
- Substitutability: A child class should enhance, not alter or diminish, the behavior of the parent class.
- Safeguarding Client Code: Client code that uses the parent class should work with instances of the derived class without being aware of the change.
LSP Requirements
To ensure compliance with the Liskov Substitution Principle, especially in statically typed languages like C#, itâs essential to follow these guidelines:
Important
- Method Parameter Types: In C#, the types of parameters in methods of a subclass should match or be more abstract than those in the parent class methods. This ensures that any method expecting a parent class instance will operate correctly if a subclass instance is passed.
- Method Return Types: The return type in methods of a subclass should match or be a subtype of the return type in the parent class methods in C#. This maintains consistency in what the client code expects to receive, preventing errors and maintaining integrity.
These rules are built into C#âs type system and are crucial for ensuring that derived classes enhance rather than disrupt the functionality of their base classes. By adhering to these requirements, developers can leverage polymorphism effectively, promoting code reusability and scalability while adhering to LSP.
Practical Example
Letâs explore how the LSP can be applied and violated, emphasizing the need for thoughtful class design:
BEFORE
BEFORE: Misapplication of inheritance can lead to a scenario where subclasses are not interchangeable with their base class without errors.
This example demonstrates a fundamental violation of LSP where the derived class
Circle
changes the inherent behavior expected from its base classTriangle
. This misuse of inheritance leads to incorrect and unexpected results, showcasing that the derived class is not a proper substitute for the base class.AFTER
AFTER: Proper implementation using interfaces to ensure compliance with LSP, enhancing flexibility and correctness.
In the corrected example, by abstracting the functionality into a
Shape
interface and ensuring bothTriangle
andCircle
classes adhere to this interface without altering the expected behavior, we maintain LSP compliance. This approach allows objects ofTriangle
andCircle
to be used interchangeably without breaking the functionality or clientâs expectations, embodying the core of LSP.Summary
Link to originalSummary
The Liskov Substitution Principle (LSP) ensures that you can use subclasses wherever you use their parent classes without causing problems in your program. Itâs really important for making software thatâs dependable and solid when youâre using inheritance. When developers stick to LSP, they can make systems that are simpler to manage and grow. It means they can make changes and improvements without making mistakes. Following LSP helps create a more predictable and sturdy structure for applications, encouraging good software practices and design.
ISP
Interface Segregation Principle
Overview
Clients should not be forced to depend on interfaces they do not use.
The Interface Segregation Principle (ISP) suggests creating interfaces that are specific and focused, avoiding whatâs called âinterface pollution.â Instead of one big interface, itâs better to have several smaller ones that suit different needs. When you follow ISP, your software design becomes tidier and easier to work with. It means you can maintain and add new features more smoothly.
Abstract
ISP emphasizes the importance of making interfaces to client needs, thereby avoiding the compulsion for clients to implement irrelevant interface methods. This leads to a codebase thatâs easier to navigate and update, with minimal side effects from changes.
Content
Principle Explained
ISP directs us to craft interfaces that are:
Important
- Narrow and Focused: Interfaces should be specific to client requirements, ensuring that classes only implement what they need.
- Client-Centric: Design interfaces with the clientâs use in mind, providing them with the functionality they require without the burden of unused methods.
- Granular and Decoupled: Segregate large, âfatâ interfaces into smaller, more coherent ones to reduce the impact of changes and improve adaptability.
Practical Example
To illustrate the Interface Segregation Principle (ISP), letâs examine a library intended to facilitate integration with various cloud services. Weâll see how applying ISP can refine our approach to designing interfaces for these services.
BEFORE
BEFORE : A single interface may contain methods not universally applicable to all cloud providers.
In this scenario,
Dropbox
is forced to implement methods that are not relevant to its services, leading to unnecessary and potentially error-prone code.AFTER
AFTER: After refactoring, we separate the single âfatâ interface into multiple, more focused interfaces, allowing each service provider to implement only the interfaces relevant to their capabilities.
Now,
Amazon
can continue providing a full spectrum of services by implementing all the relevant interfaces, whileDropbox
implements only theICloudStorageProvider
interface, leading to a clean and maintainable code structure that adheres to ISP.Warning
As with the other principles, you can go too far with this one. Donât further divide an interface which is already quite specific. Remember that the more interfaces you create, the more complex your code becomes. Keep the balance
Summary
Link to originalSummary
The Interface Segregation Principle plays a vital role in building software architectures that are flexible, easy to maintain, and capable of scaling up. When interfaces are designed to be specific and focused on what clients need, developers can build systems that can handle changes well and meet the requirements of different clients. This principle is at the core of object-oriented design, delivering on its promise of creating software thatâs adaptable and user-centered.
DIP
Dependency Inversion Principle
Overview
High-level modules should not be dependent on low-level modules; both should rely on abstractions. Additionally, these abstractions should not be based on details; rather, the details should be based on abstractions.
The Dependency Inversion Principle (DIP) is crucial for building software thatâs modular and simple to maintain. It guides the development of a system where different parts are not tightly connected but can work together through abstract interfaces. This means that the main logic of the program doesnât depend on the nitty-gritty details of how things are done at a lower level.
Content
Principle Explained
The principle distinguishes between two types of classes:
- High-Level Modules: Contain complex business logic that directs low-level classes to do something.
- Low-Level Modules: Implement basic operations such as working with a disk, transferring data over a network, connecting to a database, etc.
To ensure compliance with DIP follow this rules:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
Practical Example
BEFORE
Initially, a high-level module is directly dependent on a low-level module, leading to a rigid and fragile structure that is challenging to test and maintain.
AFTER
By inverting the dependency, both high-level and low-level modules rely on shared abstractions. This results in a more flexible and maintainable system where changes to low-level details have minimal impact on high-level modules.
This approach not only enhances code reusability and maintainability but also simplifies unit testing by allowing for easy mocking or stubbing of the lower-level components.
Summary
Link to originalSummary
Following the Dependency Inversion Principle brings flexibility and easier maintenance to software systems. It encourages a setup where the core business logic is neatly separated from the technical details of how things are done. This separation is crucial for a well-designed application.
Summary
Summary
The SOLID principles are essential in object-oriented design, providing a blueprint for building software that can withstand changes and be iterated upon easily. They act as a roadmap for crafting systems that are simpler to debug, test, and maintain, embodying the fundamental principles of successful software development.
References
Pictures from - The S.O.L.I.D Principles in Pictures | Medium
Examples from - Refactoring and Design Patterns