SOLID Principles
SOLID principles are comprised of five individual principles for writing better software. They are created by experienced developers for better guidance and to reduce complexities for writing better code that is testable and maintainable.
Single Responsibility Principle
It states that each software module should have only one responsibility or, one and only one reason to change.
A module refers to a class or a method. In an application, each class or method defines what the application does and how it does it. We can often improve the design of the software if we can separate the what from how through delegation and encapsulation.
When does the responsibility of a module change?
Responsibilities change at different times for different reasons. Each one is the axis of change. The potential source of changes to your application can help you identify when you might be violating SRP.
Any changes/extensions/improvements approved by the superiors in the functionality of the module.
Tight coupling
We can also follow the separation of concerns principle that suggests the programs should be separated into distinct sections, each addressing a separate concern.
Cohesion.
Summary
Each class should have a single responsibility or reason to change.
Strive for high cohesion and loose coupling.
Keep classes small, focused and testable.
Open / Closed Principle
It states that software entities(classes, modules, methods, etc.) should be open for extension but closed for modification. It describes how we should strive to structure our code to extend it in the future.
Benefits of OCP
Less likely to introduce bugs in code or need to redeploy.
Less downtime and less likely to break dependent code.
Code that is open for extension usually has fewer conditional statements than the code that is closed for modification which results in simpler code.
Bug fixes are easy.
Summary
OCP describes when and how we should use abstraction to make our design more extensible.
Make sure to solve the problem at hand using simple concrete code. Don't try to make your code open to extension in every possible way as it makes code way too abstract.
Identify the kinds of changes the application is likely to continue needing.
Modify the code to make it extensible along the axis of change you have identified (without the need to modify its source code every time).
Liskov Substitution Principle
It states that the subtypes must be substitutable for their base types. It also states that IS-A relationship is insufficient and should be replaced with IS-SUBSTITUABLE_FOR.
It guides how to properly use inheritance. LSP also tends to produce code that better follows OCP.
Detecting LSP violations in the code
Type checking with is or as in polymorphic code
Null checks
NotImplementedException
Fixing LSP Violations
Minimize null checks with C# features (null conditional operators and null coalescing operators), Guard classes and null object design patterns.
Follow ISP and be sure to fully implement interfaces.
Summary
The IS-A relationship is insufficient for OOP design. You need to ensure the subtypes are substitutable for base types.
Types often have invariants that must be maintained in the code that uses these types depends upon. You must take care not to break these invariants when you create subtypes or implement interfaces.
Interface Segregation Principle
It describes how we should design and use interfaces in our applications. Clients should not be forced to depend on methods they do not use. The corollary to this is that you should prefer small, cohesive interfaces to large fat ones.
What does Interface mean in ISP?
C# interface type/keyword
The public (or accessible) interface of a class
A type's interface in this context is whatever can be accessed by client code working with an instance of that type.
Detecting ISP violations in your code
Large Interfaces
NotImplementedException
Code uses just a small subset of the larger interface
ISP is related to LSP, cohesion, SRP and pain-driven development.
Fixing ISP Violations
Break up large interfaces into smaller ones.
If the original fat interface is still being used, you can compose it from smaller ones for backward compatibility.
It's easy to follow ISP if you let your client code define the interfaces the code will work with and own it.
Summary
Prefer small, cohesive interfaces to large expansive ones.
Following ISP helps with SRP and LSP.
Break up large interfaces by using interface inheritance and the adapter design pattern.
Dependency Inversion Principle
It states that high-level modules should not depend on low-level modules. Both should depend on abstraction. Abstraction should not depend on details rather details should depend on abstractions.
What is Abstraction?
Abstraction in C# refers primarily to interfaces and abstract base classes
Interfaces are generally preferred because they don't require object inheritance which makes them a bit more flexible.
The abstractions just define a contract, a way of working with a type without actually specifying how that work is going to get done by having a specific implementation of that contract.
What is the Details?
Abstractions shouldn't be coupled to details. That is they shouldn't know about the specifics of how they are implemented.
Dependency Injection
Dont create your dependencies
Depend on abstractions
Request dependencies from the client.
The client injects the dependencies as
Constructor arguements
Properties
Method arguments
The dependency injection technique is an implementation of the strategy design pattern.
Tip - Prefer constructor injection
Follows explicit dependencies principle by ensuring client code is aware of what dependencies a given class has.
It ensures an instance of the class is always in an initialized state (no need to check if properties are set before using)
Can leverage an IOC container to construct types and their dependencies.
Summary
Most classes should depend on abstractions, not implementation details.
Abstraction shouldn't leak details.
Classes should be explicit about their dependencies.
Clients should inject dependencies when they create other classes.
Structure your solutions to leverage dependency inversion.