Mastering SOLID Principles in C#

Mastering SOLID Principles in C#

When developing software, it’s essential to create code that is not only functional but also easy to maintain, extend, and understand. The SOLID principles, introduced by Robert C. Martin, provide a solid foundation for writing clean and maintainable code in object-oriented programming. In this article, we will explore each principle and provide simple C# code examples to demonstrate how they can be applied in real-world scenarios.


1. Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change, meaning it should have one job or responsibility.

Example:

public class Invoice
{
    public void GenerateInvoice() 
    {
        // Generate invoice logic
    }

    public void SendEmail()
    {
        // Email sending logic (this violates SRP)
    }
}

// Refactored version:
public class Invoice
{
    public void GenerateInvoice() 
    {
        // Generate invoice logic
    }
}

public class EmailService
{
    public void SendEmail()
    {
        // Email sending logic
    }
}        

Explanation: By separating the responsibilities of generating the invoice and sending an email, we are adhering to SRP. Each class now has a single responsibility.


2. Open/Closed Principle (OCP)

Definition: Software entities should be open for extension but closed for modification.

Example:

public class Discount
{
    public virtual double Calculate(double price)
    {
        return price;
    }
}

public class SeasonalDiscount : Discount
{
    public override double Calculate(double price)
    {
        return price * 0.9; // 10% seasonal discount
    }
}        

Explanation: The Discount class is open for extension by inheriting from it, but its original code remains unchanged, which follows the OCP.


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:

public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("Bird is flying.");
    }
}

public class Penguin : Bird
{
    public override void Fly()
    {
        throw new NotImplementedException("Penguins can't fly!"); // Violates LSP
    }
}        

Explanation: In this example, Penguin violates LSP because it can't fly but inherits a method that expects it to. To adhere to LSP, avoid this inheritance and create a more suitable class hierarchy.


4. Interface Segregation Principle (ISP)

Definition: Clients should not be forced to implement interfaces they do not use.

Example:

public interface IPrinter
{
    void Print();
    void Scan(); // Not all printers can scan
}

// Refactored version:
public interface IPrint
{
    void Print();
}

public interface IScan
{
    void Scan();
}

public class BasicPrinter : IPrint
{
    public void Print()
    {
        // Printing logic
    }
}

public class MultiFunctionPrinter : IPrint, IScan
{
    public void Print()
    {
        // Printing logic
    }

    public void Scan()
    {
        // Scanning logic
    }
}        

Explanation: By splitting the interface into smaller, more specific interfaces, we ensure that classes implement only what they need.


5. Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.

Example:

public class LightBulb
{
    public void TurnOn()
    {
        Console.WriteLine("LightBulb turned on.");
    }
}

public class Switch
{
    private LightBulb _lightBulb = new LightBulb();

    public void On()
    {
        _lightBulb.TurnOn();
    }
}

// Refactored version:
public interface IDevice
{
    void TurnOn();
}

public class LightBulb : IDevice
{
    public void TurnOn()
    {
        Console.WriteLine("LightBulb turned on.");
    }
}

public class Switch
{
    private IDevice _device;

    public Switch(IDevice device)
    {
        _device = device;
    }

    public void On()
    {
        _device.TurnOn();
    }
}        

Explanation: By depending on an abstraction (IDevice) instead of a concrete class, the Switch class becomes more flexible and follows the DIP.


Conclusion:

Applying SOLID principles in your C# code can drastically improve its maintainability, scalability, and readability. By following these principles, you ensure that your codebase remains clean and adaptable to future changes, reducing technical debt and development time.

Thanks for reading!


Jonathan Iha

Programador C#, Desenvolver .Net, Entity Framework, SQL Server, Formado em Jogos Digitais, Estudando AWS Lambda

2 个月

"Great post! ?? Your explanation of the SOLID principles in C# is clear and insightful. It’s crucial for developers to understand and apply these principles to write clean, maintainable, and scalable code. Thanks for sharing your knowledge and helping the community grow.

Guilherme Bayer

Senior Fullstack Developer | Software Engineer | 10+ years | LATAM | Javascript | Python | React | Node.js

2 个月

Diving into SOLID principles is key for clean code, huh? Those C# examples must really hit the mark. What’s your favorite principle to implement?

Diogo de Souza

Senior Software Engineer | TypeScript | Node.js | Nest.js | React | Next.js | 5x AWS Certified

2 个月

This is gold! Thanks for breaking it down so well, Lucas Wolff ??

Ericlefyson Silva

Senior Software Engineer | Front-End developer | Mobile Engineer | React | Next.js | TypeScript | Flutter

2 个月

The power of SOLID principles for build a scalable aplication is really amazing, this post is very necessary!

Rodrigo Dias

Senior React | React Native Developer

2 个月

Interesting! Thanks for sharing.

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

社区洞察

其他会员也浏览了