Master SOLID Principles & write better Code

Master SOLID Principles & write better Code

SOLID Principles in C#: Building Robust and Maintainable Code

The SOLID principles are a set of five design principles in object-oriented programming intended to make software designs more understandable, flexible, and maintainable. They were introduced by Robert C. Martin, also known as Uncle Bob. Each principle addresses a particular aspect of software design, contributing to creating robust and scalable software systems. By adhering to these principles, you can create applications that are easier to understand, test, and modify in the long run. Let’s delve into each principle with explanations and C# examples:

1. Single Responsibility Principle (SRP):

  • What it is: Every class or module should have one, and only one, reason to change.
  • Benefits: Easier to understand, test, and maintain individual classes.

Example:

C#

// Violation of SRP (combined login and user management)
public class UserService
{
    public bool Login(string username, string password)
    {
        // Login logic
    }

    public void CreateUser(string username, string email)
    {
        // User creation logic
    }
}

// Improved version with SRP (separate classes)
public class AuthenticationService
{
    public bool Login(string username, string password)
    {
        // Login logic
    }
}

public class UserService
{
    public void CreateUser(string username, string email)
    {
        // User creation logic
    }
}

2. Open/Closed Principle (OCP):

  • What it is: Software entities (classes, modules) should be open for extension, but closed for modification.
  • Benefits: Enables adding new functionalities without modifying existing code.

Example:

C#

// Violation of OCP (hardcoded shape calculation)
public class ShapeCalculator
{
    public double CalculateArea(Square square)
    {
        return square.SideLength * square.SideLength;
    }

    public double CalculateArea(Circle circle)
    {
        return Math.PI * circle.Radius * circle.Radius;
    }
}

// Improved version with OCP (abstract class and inheritance)
public abstract class Shape
{
    public abstract double CalculateArea();
}

public class Square : Shape
{
    public double SideLength { get; set; }

    public override double CalculateArea()
    {
        return SideLength * SideLength;
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

// Add new shapes without modifying existing code
public class Triangle : Shape
{
    public double Base { get; set; }
    public double Height { get; set; }

    public override double CalculateArea()
    {
        return 0.5 * Base * Height;
    }
}

3. Liskov Substitution Principle (LSP):

  • What it is: Objects of a subtype should be replaceable with objects of their supertype (parent class) without altering the program’s correctness.
  • Benefits: Ensures consistency and type safety in hierarchies.

Example:

C#

// Violation of LSP (Rectangle doesn't follow Square behavior)
public class Square : Rectangle
{
    public override void SetWidth(double width)
    {
        base.SetWidth(width);
        base.SetHeight(width); // Square has equal sides
    }

    public override void SetHeight(double height)
    {
        base.SetHeight(height);
        base.SetWidth(height); // Square has equal sides
    }
}

// Shape should not allow negative dimensions
public class Shape
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }

    public virtual void SetWidth(double width)
    {
        if (width < 0)
        {
            throw new ArgumentOutOfRangeException("Width cannot be negative");
        }

        Width = width;
    }

    public virtual void SetHeight(double height)
    {
        if (height < 0)
        {
            throw new ArgumentOutOfRangeException("Height cannot be negative");
        }

        Height = height;
    }
}

4. Interface Segregation Principle (ISP):

  • What it is: Clients shouldn’t be forced to depend on methods they don’t use. Favor many, smaller interfaces over one large interface.
  • Benefits: Promotes loose coupling and improves flexibility.

Example:

C#

// Violation of ISP (one interface with all functionalities)
public interface IShape
{
    double CalculateArea();
    void Draw(); // Not required by all shapes (e.g., point)
}

// Improved

5. Dependency Inversion Principle (DIP):

  • What it is: High-level modules (classes) should not depend on low-level modules (classes); both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
  • Benefits: Enables easier testing, looser coupling, and promotes separation of concerns.

Example:

C#

// Bad design
public class WeatherTracker
{
    private readonly WeatherStation _weatherStation;

    public WeatherTracker()
    {
        _weatherStation = new WeatherStation();
    }

    public string GetWeather()
    {
        return _weatherStation.GetWeather();
    }
}

// Good design
public interface IWeatherSource
{
    string GetWeather();
}

public class WeatherTracker
{
    private readonly IWeatherSource _weatherSource;

    public WeatherTracker(IWeatherSource weatherSource)
    {
        _weatherSource = weatherSource;
    }

    public string GetWeather()
    {
        return _weatherSource.GetWeather();
    }
}

By understanding and applying these SOLID principles, you can write cleaner, more maintainable, and scalable C# code that can evolve efficiently as your applications grow.

You can take read more about SOLID principles form here:

Leave a Reply

Your email address will not be published. Required fields are marked *