Поиск  
Always will be ready notify the world about expectations as easy as possible: job change page
Nov 11

Understanding SOLID principles in .NET (C#): A practical guide with code examples

Understanding SOLID principles in .NET (C#): A practical guide with code examples
Автор:
Источник:
Просмотров:
251

SOLID principles make it easy for a developer to write easily extendable code and avoid common coding errors.

These principles were introduced by Robert C. Martin, and they have become a fundamental part of object-oriented programming.

In the context of .NET development, adhering to SOLID principles can lead to more modular, flexible, and maintainable code. In this article, we’ll delve into each SOLID principle with practical coding examples in C#.

Following are the five SOLID design principles:

SOLID principles

1. Single Responsibility Principle (SRP)

The SRP states that a class should have only one reason to change, meaning it should have only one responsibility. This promotes modularization and makes the code easier to understand and maintain.

Key idea: A class should do only one thing, and it should do it well.

Real-time example: Think of a chef who only focuses on cooking, not managing the restaurant or delivering food.

Practical coding example in C#:

Before applying for SRP:

public class Report
{
    public void GenerateReport() { }
    public void SaveToFile() { }
}

In this scenario, the Report class has two responsibilities: generating a report and saving it to a file. This violates the SRP.

After applying SRP:

public class Report
{
    public void GenerateReport() { }
}

public class ReportSaver
{
    public void SaveToFile() { }
}

Now, the Report class is responsible only for generating reports, while the ReportSaver class is responsible for saving reports. Each class has a single responsibility.

Explanation: According to SRP, one class should take one responsibility hence to overcome this problem we should write another class to save the report functionality. If you make any changes to theReport class will not affect the ReportSaver class.

2. Open/Closed Principle (OCP)

The Open/Closed Principle suggests that a class should be open for extension but closed for modification. This means you can add new features without altering existing code.

Key idea: Once a class is written, it should be closed for modifications but open for extensions.

Real-time example: Your smartphone — you don’t open it up to add features; you just download apps to extend its capabilities.

Practical coding example in C#:

Before applying for OCP:

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

public class AreaCalculator
{
    public double CalculateArea(Rectangle rectangle)
    {
        return rectangle.Width * rectangle.Height;
    }
}

This design may become problematic when adding new shapes. Modifying the AreaCalculator for each new shape violates the OCP.

After applying for OCP:

public interface IShape
{
    double CalculateArea();
}

public class Rectangle : IShape
{
    // implementation
}

public class Circle : IShape
{
    // implementation
}

By introducing an interface (IShape), new shapes like Circle can be added without modifying existing code, adhering to the OCP.

Explanation: According to OCP, the class should be open for extension but closed for modification. So, When you introduce a new shape, then just implement it from the interface IShape. So IShape is open for extension but closed for further modification.

3. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

Key idea: You should be able to use any subclass where you use its parent class.

Real-time example: You have a remote control that works for all types of TVs, regardless of the brand.

Practical coding example in C#:

Before applying for LSP:

public class Bird
{
    public virtual void Fly() { /* implementation */ }
}

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

Here, the Penguin class violates the LSP by throwing an exception for the Fly method.

After applying for LSP:

public interface IFlyable
{
    void Fly();
}

public class Bird : IFlyable
{
    public void Fly()
    {
        // implementation specific to Bird
    }
}

public class Penguin : IFlyable
{
    public void Fly()
    {
        // implementation specific to Penguins
        throw new NotImplementedException("Penguins can't fly!");
    }
}

By introducing the IFlyable interface, both Bird and Penguin adhere to the Liskov Substitution Principle.

Explanation: According to LSP, a derived class should not break the base class’s type definition and behavior which means objects of a base class shall be replaceable with objects of its derived classes without breaking the application. This needs the objects of derived classes to behave in the same way as the objects of your base class.

4. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that a class should not be forced to implement interfaces it does not use. This principle encourages the creation of small, client-specific interfaces.

Key idea: A class should not be forced to implement interfaces it doesn’t use.

Real-time example: You sign up for a music streaming service and only choose the genres you like, not all available genres.

Practical coding example in C#:

Before applying for ISP:

public interface IWorker
{
    void Work();
    void Eat();
}

public class Manager : IWorker
{
    // implementation
}

public class Robot : IWorker
{
    // implementation
}

The Robot class is forced to implement the Eat method, violating ISP.

After applying for ISP:

public interface IWorkable
{
    void Work();
}

public interface IEatable
{
    void Eat();
}

public class Manager : IWorkable, IEatable
{
    // implementation
}

public class Robot : IWorkable
{
    // implementation
}

By splitting the IWorker interface into smaller interfaces (IWorkable and IEatable), classes can implement only what they need, adhering to ISP.

Explanation: According to LSP, any client should not be forced to use an interface that is irrelevant to it. In other words, clients should not be forced to depend on methods that they do not use.

5. Dependency Inversion Principle (DIP)

The Dependency Inversion Principle suggests that high-level modules should not depend on low-level modules, but both should depend on abstractions. Additionally, abstractions should not depend on details; details should depend on abstractions.

Key idea: High-level modules should not depend on low-level modules; both should depend on abstractions.

Real-time example: Building a LEGO tower — the bricks (high and low-level modules) connect through smaller bricks (abstractions).

Practical coding example in C#:

Before applying DIP:

public class LightBulb
{
    public void TurnOn() { /* implementation */ }
    public void TurnOff() { /* implementation */ }
}

public class Switch
{
    private LightBulb bulb;

    public Switch(LightBulb bulb)
    {
        this.bulb = bulb;
    }

    public void Toggle()
    {
        if (bulb.IsOn)
            bulb.TurnOff();
        else
            bulb.TurnOn();
    }
}

The Switch class directly depends on the concrete LightBulb class, violating DIP.

After applying DIP:

public interface ISwitchable
{
    void TurnOn();
    void TurnOff();
}

public class LightBulb : ISwitchable
{
    // implementation
}

public class Switch
{
    private ISwitchable device;

    public Switch(ISwitchable device)
    {
        this.device = device;
    }

    public void Toggle()
    {
        if (device.IsOn)
            device.TurnOff();
        else
            device.TurnOn();
    }
}

By introducing an interface (ISwitchable), the Switch class now depends on an abstraction, adhering to the Dependency Inversion Principle.

Explanation: According to DIP, do not write any tightly coupled code because that is a nightmare to maintain when the application is growing bigger and bigger. If a class depends on another class, then we need to change one class if something changes in that dependent class. We should always try to write loosely coupled classes.

Conclusion

Remember, by understanding and applying these SOLID principles, .NET developers can create more robust, flexible, and maintainable software. It’s important to note that these principles work together and complement each other, contributing to the overall design philosophy of object-oriented programming.

Похожее
Apr 29
Author: Salman Karim
Background tasks are crucial in many software applications, and scheduling them to run periodically or at specific times is a common requirement. In the .NET ecosystem, Quartz.NET provides a robust and flexible framework for scheduling such tasks. Let’s delve into...
17 апреля
Рассмотрим интересную задачу по разработке игры «Крестики Нолики» на языке C#. Наш проект будет запускаться в консоли и потребует креативное мышление для решения задачи.  Ваша задача — реализовать консольную игру "крестики-нолики" с использованием языка программирования C#. Вам нужно создать игровое...
Jul 18
Author: Vinod Pal
In today’s fast-paced business environment, efficiency and responsiveness are crucial for maintaining a competitive edge. Background batch processing plays a pivotal role in achieving these goals by handling time-consuming tasks asynchronously, thereby freeing up system resources and ensuring a seamless...
Dec 23, 2023
Author: Matt Bentley
A guide to implementing value objects — Domain-Driven Design’s most powerful, yet least understood and utilized building block Generated using DALL-E 3 Value Objects are one of the most powerful building blocks in Domain-Driven Design for creating rich models, however...
Написать сообщение
Тип
Почта
Имя
*Сообщение
RSS
Если вам понравился этот сайт и вы хотите меня поддержать, вы можете
Стили именования переменных и функций. Используйте их все
10 историй, как «валят» айтишников на технических интервью
Функции и хранимые процедуры в PostgreSQL: зачем нужны и как применять в реальных примерах
Soft skills: 18 самых важных навыков, которыми должен владеть каждый работник
Семь итераций наивности или как я полтора года свою дебютную игру писал
Вопросы с собеседований, которые означают не то, что вы думаете
Путеводитель по репликации баз данных
5 приемов увеличения продуктивности разработчика
Топ 8 лучших ресурсов для практики программирования в 2018
Использование SQLite в .NET приложениях
LinkedIn: Sergey Drozdov
Boosty
Donate to support the project
GitHub account
GitHub profile