Overview
solid principles c# a coding standard that all developers should have a clear concept for writing code in a proper way and avoid a bad coding architecture. These are the five pillars of coding guidelines of programming languages. It was promoted by Robert C Martin and is used across the object-oriented design models.
Why are SOLID principles required?
When a developer develops software with poor coding structure, the code can become inflexible and more brittle, small changes in the code can result in bugs. To overcome these, we should follow solid principles c#.
S – Single responsibility principle
A class should have one and only one job. Single responsibility with solid principles c#, so you will have many small classes each of them doing only one thing or task.
Advantages:
- Testing – A class with one responsibility will have far fewer test cases for this class.
- Lower coupling – Less functionality in a single class will have fewer dependencies.
- Organization – Smaller, well-organized classes are easier to search than monolithic ones.
Now we’ll take an example to understand this principle.
Here, the Music class takes 2 responsibilities, one is to take responsibility for playing music and another one is to pause music.
public class Music { public void PlayMusic() { // Code for play music } public void PauseMusic() { // Code for pause music } }
So according to SRP, one class should take one responsibility so we should write one different class for playing music so that any change in playing music should not affect the music class.
public class MusicPlay { public void PlayMusic() { // Code for play music } }
O – Open closed principle
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This principle suggests that the class should be easily extended but there is no need to change its core implementations.
We can make sure that our code is compliant with the open/closed principle by utilizing inheritance and/or implementing interfaces that enable classes to polymorphically substitute for each other.
Example
public class Music { public string actionName {get; set;} public void MusicAction() { if (actionName == “play”) { // Code for play music } if (actionName == “pause”) { // Code for pause music } } }
Here we have written too many ‘if‘ clauses and if we want to introduce another new action like ‘repeat’, then you need to write another ‘if‘. This class should be open for extension but closed for modification. But how to do that?
public class Music { public virtual void Music() { // From base } } public class PlayMusic : Music { public override void PlayMusic() { // Code for play music } } public class PauseMusic : Music { public override void PauseMusic() { // Code for pause music } }
So if you want to introduce a new action, then just inherit from Music. So Music is open for extension but closed for modification.
L – Liskov substitution principle
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. To put it more simply, if class A is a subtype of class B, then we should be able to replace B with A without disrupting the behavior of our program.
Example
public class Banana { public virtual string GetColor() { return "Yellow"; } } public class Watermelon : Banana { public override string GetColor() { return "Green"; } } public class Program { private static void Main(string[] args) { Banana banana = new Watermelon(); Console.WriteLine(banana.GetColor()); } }
Let’s refactor and make it comply with LSP by having a generic base class for both Banana and Watermelon.
public abstract class Fruit { public abstract string GetColor(); } public class Banana : Fruit { public override string GetColor() { return "Yellow"; } } public class Watermelon : Banana { public override string GetColor() { return "Green"; } } public class Program { private static void Main(string[] args) { Fruit fruit = new Watermelon(); Console.WriteLine(fruit.GetColor()); fruit = new Banana(); Console.WriteLine(fruit.GetColor()); } }
I – Interface segregation principle
A client should never be forced to implement an interface that it doesn’t use or clients shouldn’t be forced to depend on methods they do not use.
Example:
For example we have one DB to store all car details.
public interface ICar { bool AddCarDetails(); }
And all types of car classes will inherit this interface for saving data. Right now this is good for you, right? Now suppose you are asked to fetch the details of a car by using the car name.
What you will do in that case is just add one method to this interface, right?
public interface ICarDB { bool AddCarDetails(); bool ShowAnyCarDetails(string carName); }
Here we only want to show the car details from DB. So, the solution based on the interface segregation principle is to give this responsibility to another interface.
public interface IAddCarOperation { bool AddCarDetails(); } public interface IGetCarOperation { bool ShowAnyCarDetails(string carName); }
Now, we use IAddCarOperation interface to add car details and the IGetCarOperation interface used to get a car’s details.
D – Dependency Inversion principle
Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but it should depend on abstractions.
We say in simple word dependency injection is used simply by ‘injecting’ any dependencies of a class through the class constructor as an input parameter.
class Car { private LogManager logManager = new LogManager (); void AddCar(Database db, string carName) { try { db.Add(carName); } catch (Exception ex) { logManager.log(ex.ToString()); } } }
Now here we can create the LogManager instance from within the Car class.
This is a violation of the dependency inversion principle.
If we wanted to use a different kind of logger, we would have to modify the Car class.
Let’s see how it can be fixed by using dependency injection.
class Car { private LogManager _logManager ; public Post(LogManager injectedLoggerHere) { _logManager = injectedLoggerHere; } void AddCar(Database db, string carName) { try { db.Add(carName); } catch (Exception ex) { _logManager.log(ex.ToString()); } } }
Conclusion
Solid principles are the most important set of principles that we should learn and implement as programmers. By using these, we can reduce complexity, execution time and can complete our work with fewer exceptions, in very less time and an understandable manner. I must say you should also follow these principles while coding.