SOLID Principles

SOLID Principles

The SOLID principles are a set of five design principles for writing maintainable, scalable, and robust object-oriented software.

1) Single Responsibility Principle (SRP):

The Single Responsibility Principle (SRP) is a design principle in object-oriented programming that states that a class should have only one reason to change, meaning it should have only one responsibility or job. In simpler terms, it suggests that a class should encapsulate one and only one aspect of functionality, and that aspect should be entirely encapsulated by the class.

Example:

// Bad design (violates SRP)
class Customer
{
    public void AddCustomer() { /* code for adding a customer */ }
    public void GenerateInvoice() { /* code for generating an invoice */ }
    public void SendEmail() { /* code for sending an email */ }
}

// Good design (adheres to SRP)
class Customer
{
    public void AddCustomer() { /* code for adding a customer */ }
}

class InvoiceGenerator
{
    public void GenerateInvoice() { /* code for generating an invoice */ }
}

class EmailSender
{
    public void SendEmail() { /* code for sending an email */ }
}        

2) Open/Closed Principle (OCP):

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

In simpler terms, it means that the behavior of a module should be extendable without modifying its source code. This is typically achieved through the use of abstraction and polymorphism.

Here's what it entails:

  1. Open for extension: This means that you should be able to add new functionality or behavior to a module without altering its existing code. You can achieve this by designing your modules to be easily extended through inheritance or composition.
  2. Closed for modification: Once a module is written and tested, its source code should not be modified. Any changes or enhancements should be made by adding new code, rather than changing existing code. This ensures that the existing behavior remains intact and reduces the risk of introducing bugs.

Example:

// Bad example - violating OCP
class Circle
{
    public double Radius { get; set; }
}

class AreaCalculator
{
    public double CalculateArea(Circle circle)
    {
        return Math.PI * Math.Pow(circle.Radius, 2);
    }
    
    // Later requirement to calculate the area of a rectangle
    public double CalculateArea(Rectangle rectangle)
    {
        return rectangle.Width * rectangle.Height;
    }
}

// Good example - adhering to OCP
interface IShape
{
    double CalculateArea();
}

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

    public double CalculateArea()
    {
        return Math.PI * Math.Pow(Radius, 2);
    }
}

class Rectangle : IShape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double CalculateArea()
    {
        return Width * Height;
    }
}        

3) Liskov Substitution Principle (LSP):

The principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In other words, a subclass should be substitutable for its superclass without altering the desirable properties of the program, such as correctness, performance, and behavior.

Example:

// Bad example - violating LSP
class Rectangle
{
    public virtual double Width { get; set; }
    public virtual double Height { get; set; }
}

class Square : Rectangle
{
    public override double Width
    {
        get => base.Width;
        set
        {
            base.Width = value;
            base.Height = value;
        }
    }

    public override double Height
    {
        get => base.Height;
        set
        {
            base.Width = value;
            base.Height = value;
        }
    }
}

// Good example - adhering to LSP
abstract class Shape
{
    public abstract double Area { get; }
}

class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public override double Area => Width * Height;
}

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

    public override double Area => Side * Side;
}        

4) Interface Segregation Principle (ISP):

According to the ISP, a client should not be forced to implement interfaces that it doesn't use. In other words, instead of creating large interfaces that contain methods for multiple functionalities, it's better to have smaller, more specialized interfaces that are tailored to specific client needs. This helps in reducing the coupling between classes and promotes a more modular and cohesive design.

Example:

// Bad example - violating ISP
interface IShape
{
    void Draw();
    void Resize();
    void Rotate();
}

class Circle : IShape
{
    public void Draw()
    {
        // Code to draw a circle
    }

    public void Resize()
    {
        // Code to resize a circle
    }

    public void Rotate()
    {
        // Code to rotate a circle
    }
}

// Good example - adhering to ISP
interface IDrawable
{
    void Draw();
}

interface IResizable
{
    void Resize();
}

interface IRotatable
{
    void Rotate();
}

class Circle : IDrawable, IResizable
{
    public void Draw()
    {
        // Code to draw a circle
    }

    public void Resize()
    {
        // Code to resize a circle
    }
}        

5) Dependency Inversion Principle (DIP):

The Dependency Inversion Principle (DIP) is one of the five SOLID principles of object-oriented programming (OOP), which aims to create systems that are easier to maintain and extend.

In simpler terms, this principle suggests that rather than depending on concrete implementations, modules and classes should rely on abstract interfaces or classes. This abstraction helps to decouple components within a system, making it easier to replace, modify, and extend them without affecting the rest of the system.

Example:

// Bad example - violating DIP
class DataAccessLayer
{
    public void SaveData(object data)
    {
        // Code to save data to a database
    }
}

class BusinessLogicLayer
{
    private DataAccessLayer dataAccessLayer;

    public BusinessLogicLayer()
    {
        dataAccessLayer = new DataAccessLayer();
    }

    public void ProcessData(object data)
    {
        // Code to process data
        dataAccessLayer.SaveData(data);
    }
}

// Good example - adhering to DIP
interface IDataAccessLayer
{
    void SaveData(object data);
}

class DataAccessLayer : IDataAccessLayer
{
    public void SaveData(object data)
    {
        // Code to save data to a database
    }
}

class BusinessLogicLayer
{
    private IDataAccessLayer dataAccessLayer;

    public BusinessLogicLayer(IDataAccessLayer dataAccessLayer)
    {
        this.dataAccessLayer = dataAccessLayer;
    }

    public void ProcessData(object data)
    {
        // Code to process data
        dataAccessLayer.SaveData(data);
    }
}        

These examples demonstrate how the SOLID principles can be applied in C# to improve the design and maintainability of your code. Remember that applying these principles can lead to more flexible and easier-to-maintain software architectures.


Abhinav Srivastava

Senior Software Engineer @ AlsoEnergy

7 个月

Well said

要查看或添加评论,请登录

社区洞察

其他会员也浏览了