Understanding SOLID Principles in .NET C#
Introduction
SOLID principles are five design principles that help developers create more understandable, flexible, and maintainable code. Robert C. Martin introduced these principles which are widely used in object-oriented programming. Let’s break down each principle with easy-to-understand explanations and examples in .NET C#.
1. Single Responsibility Principle (SRP)
Definition: A class should have only one reason to change, meaning it should have only one job or responsibility.
Example:
Let’s say we have a class Invoice
that handles both creating invoices and sending them via email. This violates SRP because the class has more than one responsibility.
public class Invoice
{
public void CreateInvoice()
{
// Code to create an invoice
}
public void SendEmail()
{
// Code to send email
}
}
To follow SRP, we should separate these responsibilities into different classes:
public class Invoice
{
public void CreateInvoice()
{
// Code to create an invoice
}
}
public class EmailService
{
public void SendEmail()
{
// Code to send email
}
}
2. Open/Closed Principle (OCP)
Definition: Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Example:
Suppose we have a class Discount
that applies a discount to a price. If we want to add a new discount type, we should not modify the existing code.
public class Discount
{
public double ApplyDiscount(double price)
{
// Default discount
return price * 0.9;
}
}
Instead, we can extend the class:
public abstract class Discount
{
public abstract double ApplyDiscount(double price);
}
public class DefaultDiscount : Discount
{
public override double ApplyDiscount(double price)
{
return price * 0.9;
}
}
public class SpecialDiscount : Discount
{
public override double ApplyDiscount(double price)
{
return price * 0.8;
}
}
3. Liskov Substitution Principle (LSP)
Definition: Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
Example:
Consider a class Bird
and a subclass Ostrich
. If Ostrich
cannot behave as a Bird
(e.g., it cannot fly), it violates LSP.
public class Bird
{
public virtual void Fly()
{
// Flying code
}
}
public class Ostrich : Bird
{
public override void Fly()
{
throw new NotSupportedException("Ostriches can't fly!");
}
}
To follow LSP, we should redesign the classes:
public abstract class Bird
{
// Bird properties and methods
}
public class FlyingBird : Bird
{
public void Fly()
{
// Flying code
}
}
public class Ostrich : Bird
{
// Ostrich-specific properties and methods
}
4. Interface Segregation Principle (ISP)
Definition: Clients should not be forced to depend on interfaces they do not use.
Example:
If we have an interface IMultiFunctionDevice
with methods for printing, scanning, and faxing, a class implementing this interface might only need some methods.
public interface IMultiFunctionDevice
{
void Print();
void Scan();
void Fax();
}
public class Printer : IMultiFunctionDevice
{
public void Print() { }
public void Scan() { }
public void Fax() { }
}
To follow ISP, we should split the interface:
public interface IPrinter
{
void Print();
}
public interface IScanner
{
void Scan();
}
public interface IFax
{
void Fax();
}
public class Printer : IPrinter
{
public void Print() { }
}
5. Dependency Inversion Principle (DIP)
Definition: 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.
Example:
Suppose we have a UserService
a class that depends on a concrete EmailService
class.
public class EmailService
{
public void SendEmail() { }
}
public class UserService
{
private EmailService _emailService;
public UserService()
{
_emailService = new EmailService();
}
public void NotifyUser()
{
_emailService.SendEmail();
}
}
To follow DIP, we should depend on an abstraction:
public interface IEmailService
{
void SendEmail();
}
public class EmailService : IEmailService
{
public void SendEmail() { }
}
public class UserService
{
private IEmailService _emailService;
public UserService(IEmailService emailService)
{
_emailService = emailService;
}
public void NotifyUser()
{
_emailService.SendEmail();
}
}
Conclusion
By adhering to SOLID principles, you can write easier code to understand, maintain, and extend. These principles help you build robust and scalable applications in .NET C#. Happy coding!
For more updates and insights, and to connect with me, feel free to follow me on LinkedIn:
Let’s stay connected and continue the conversation!