1. Single Responsibility Principle (SRP):
This principle states that a class should have only one reason to change, meaning it should have only one responsibility.
Example:
// Before applying SRP
class Employee
{
public void CalculateSalary() { /*...*/ }
public void GenerateReport() { /*...*/ }
}
// After applying SRP
class Employee
{
public void CalculateSalary() { /*...*/ }
}
class ReportGenerator
{
public void GenerateReport() { /*...*/ }
}
2. Open-Closed Principle (OCP):
This principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In other words, you should be able to add new functionality without changing existing code.
Example:
// Before applying OCP
class OrderProcessor
{
public void ProcessOrder(Order order)
{
if (order.Type == "Online")
{
// Process online order
}
else if (order.Type == "InStore")
{
// Process in-store order
}
// More conditions for other order types...
}
}
// After applying OCP
interface IOrderProcessor
{
void ProcessOrder(Order order);
}
class OnlineOrderProcessor : IOrderProcessor
{
public void ProcessOrder(Order order) { /*...*/ }
}
class InStoreOrderProcessor : IOrderProcessor
{
public void ProcessOrder(Order order) { /*...*/ }
}
3. Liskov Substitution Principle (LSP):
This principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
Suppose we have a base class called Shape representing geometric shapes, and it has a method called Area to calculate the area of the shape:
public class Shape
{
public virtual double Area()
{
return 0.0;
}
}
Now, we create a subclass called Rectangle which represents a rectangle. It inherits from the Shape class and overrides the Area method to calculate the area of a rectangle:
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width * Height;
}
}
Here's another subclass called Circle representing a circle. It also inherits from Shape and overrides the Area method to calculate the area of a circle:
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
Now, let's demonstrate the Liskov Substitution Principle in action. We'll create a method that accepts a Shape object and calculates its area without knowing the specific type of shape:
public class AreaCalculator
{
public double CalculateArea(Shape shape)
{
return shape.Area();
}
}
With this setup, we can use the AreaCalculator to calculate the areas of various shapes without worrying about their specific types:
Shape rectangle = new Rectangle { Width = 5, Height = 4 };
Shape circle = new Circle { Radius = 3 };
AreaCalculator calculator = new AreaCalculator();
double rectangleArea = calculator.CalculateArea(rectangle);
double circleArea = calculator.CalculateArea(circle);
Console.WriteLine($"Area of rectangle: {rectangleArea}");
Console.WriteLine($"Area of circle: {circleArea}");
In this example, we're following the Liskov Substitution Principle because we're able to use instances of the Rectangle and Circle subclasses interchangeably with instances of the base class Shape. The CalculateArea method doesn't need to know the specific type of shape it's dealing with; it simply calls the Area method, which is appropriately overridden in each subclass.
This adherence to the LSP makes our code more flexible, maintainable, and easier to extend because we can add new shape subclasses without modifying existing code that works with the Shape class. 4. Interface Segregation Principle (ISP):
This principle states that clients should not be forced to depend on interfaces they do not use. In other words, it's better to have many small, specific interfaces than one large, general-purpose interface.
Example:
// Violating ISP
interface IWorker
{
void Work();
void Eat();
}
class Engineer : IWorker
{
public void Work() { /*...*/ }
public void Eat() { /*...*/ }
}
// Following ISP
interface IWorker
{
void Work();
}
interface IEater
{
void Eat();
}
class Engineer : IWorker, IEater
{
public void Work() { /*...*/ }
public void Eat() { /*...*/ }
}
5. Dependency Inversion Principle (DIP):
This principle states that high-level modules should not depend on low-level modules; both should depend on abstractions. It also advocates that abstractions should not depend on details; details should depend on abstractions.
Example:
// Without DIP
class LightBulb
{
public void TurnOn() { /*...*/ }
public void TurnOff() { /*...*/ }
}
class Switch
{
private LightBulb bulb = new LightBulb();
public void Operate()
{
// Operate the light bulb
bulb.TurnOn();
// More logic...
}
}
// Following DIP
interface ISwitchable
{
void TurnOn();
void TurnOff();
}
class LightBulb : ISwitchable
{
public void TurnOn() { /*...*/ }
public void TurnOff() { /*...*/ }
}
class Switch
{
private ISwitchable device;
public Switch(ISwitchable device)
{
this.device = device;
}
public void Operate()
{
// Operate the device (can be a light bulb, fan, etc.)
device.TurnOn();
// More logic...
}
}
By understanding and applying these SOLID principles, you can write more maintainable, extensible, and robust C# code in your real-time projects. It's essential to keep these principles in mind when designing your software architecture to achieve a high level of flexibility and ease of maintenance.
0 Comments