C# – Solid principles with C# examples
Solid principles are a set of design principles that help developers create maintainable, scalable, and flexible software. Regardless of the programming language you use, SOLID will help you write more liner, maintainable, extendable code.
The SOLID acronym stands for:
S – Single Responsibility Principle
O – Open/Closed Principle
L – Liskov Substitution Principle
I – Interface Segregation Principle
D – Dependency Inversion Principle
Let’s see them in more detail
Single Responsibility Principle
A class should have only one reason to change.
// Without Single Responsibility Principle public class Report { public void GenerateReport() { // Generate report logic } public void SaveReportToFile() { // Save report to file logic } } // With Single Responsibility Principle public class Report { public void GenerateReport() { // Generate report logic } } public class ReportSaver { public void SaveReportToFile(Report report) { // Save report to file logic } }
Open/Closed Principle
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
// Without Open/Closed Principle public class Rectangle { public double Width { get; set; } public double Height { get; set; } } public class AreaCalculator { public double CalculateArea(Rectangle rectangle) { return rectangle.Width * rectangle.Height; } } // Adding a new shape requires modifying existing code public class Circle { public double Radius { get; set; } } // With Open/Closed Principle public abstract class Shape { public abstract double CalculateArea(); } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public override double CalculateArea() { return Width * Height; } } public class Circle : Shape { public double Radius { get; set; } public override double CalculateArea() { return Math.PI * Radius * Radius; } }
Liskov Substitution Principle
Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
// Without Liskov Substitution Principle public class Bird { public virtual void Fly() { Console.WriteLine("Bird is flying"); } } public class Ostrich : Bird { public override void Fly() { // Ostrich can't fly, violates LSP Console.WriteLine("Ostrich can't fly"); } } // With Liskov Substitution Principle public interface IFlyable { void Fly(); } public class Bird : IFlyable { public void Fly() { Console.WriteLine("Bird is flying"); } } public class Ostrich : IFlyable { public void Fly() { // Ostrich can't fly, conforms to LSP Console.WriteLine("Ostrich can't fly"); } }
Interface Segregation Principle
A client should not be forced to implement interfaces it does not use.
// Without Interface Segregation Principle public interface IWorker { void Work(); void Eat(); } public class Worker : IWorker { public void Work() { // Work logic } public void Eat() { // Eat logic } } // With Interface Segregation Principle public interface IWorkable { void Work(); } public interface IEatable { void Eat(); } public class Worker : IWorkable, IEatable { public void Work() { // Work logic } public void Eat() { // Eat logic } }
Dependency Inversion Principle
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.
// Without Dependency Inversion Principle public class LightBulb { public void TurnOn() { // Turn on logic } } public class Switch { private LightBulb bulb;public Switch() { bulb = new LightBulb(); } public void Toggle() { // Toggle logic bulb.TurnOn(); } } // With Dependency Inversion Principle public interface ISwitchable { void TurnOn(); } public class LightBulb : ISwitchable { public void TurnOn() { // Turn on logic } } public class Switch { private ISwitchable device; public Switch(ISwitchable device) { this.device = device; } public void Toggle() { // Toggle logic device.TurnOn(); } }
These simple examples will help us remember