SOLID Principles In C-sharp With Examples

Author : Sachin Sharma
Published On : 21 Nov 2024
Tags: c# dotnet-core

SOLID Principles are the key aspects of object oriented programming language which help developers to write clean, manageable scalable code.how can implement SOLID principles in c# and dotnet core.

S - Single Responsibility Principle
O - Open/Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle

1) Single Responsibility Principle (SPR)

A class should have one job or responsibility to do. should have no reason to change.each class should have single task to do.if it is doing more than one task thats mean voilating SPR.

Example : Without SPR 

 public class OrderController : Controller
    {
        private readonly OrderService _orderService;
        public OrderController(OrderService orderService)
        {
            _orderService = orderService;
        }
        public async Task<IActionResult> CreateOrder(Order req)
        {
            _orderService CreateOrder(req);
            // CreateOrder will perform two responsibility creare order and create invoice.
        }
    }
    public class OrderService
    {
        public void CreateOrder(Order order)
        {
            // creare order logic here
            // Create Invoice logic here
            InvoiceService s = new InvoiceService();
            s.CreateInvoice();
        }
    }
    public class InvoiceService
    {
        public void CreateInvoice()
        {
            // create invoice logic
        }
    }

Invoice logic in AddOrder is voilating SPR

Example: With SPR 

public class OrderController : Controller
    {
        private readonly OrderService _orderService;
        private readonly InvoiceService _invoiceService;
        public OrderController(OrderService orderService, InvoiceService invoiceService)
        {
            _orderService = orderService;
            _invoiceService = invoiceService;
        }
        public async Task<IActionResult> CreateOrder(Order req)
        {
            _orderService CreateOrder(req);
            _invoiceService.CreateInvoice(req);
        }
    }
    public class InvoiceService
    {
        public void CreateInvoice()
        { /*Create Invoice Logic Here */}
    }
    public class OrderService
    {
        public void AddOrder(Order order)
        {
            // add order logic here  
        }
    }

Now Both OrderService and Invoice service has own responsibility.OrderService has no longer
responsibility of create invoice.

2) Open/Closed Principle (OCP)

It state that a class,function should be open for extension but closed for modification.this principle promote the idea that you should be able to add new functionality in class or module without modify existing code.

We need to consider two things.first is Open for extension and the second is closed for modification.lets understand with example.

public class DiscountService
    {
        public double GetDiscountAmount(Order order)
        {
            if (order.CustomerType == "VIP")
                return order.Amount * 0.1;
            else if (order.CustomerType == "Regular")
                return order.Amount * 0.05;
            else
                return 0;
        }
    }

This code voilating OCP principle because if we need to add more discount type we need to modify the existing code of GetDiscountAmount function.

Now the question is how to implement Open/Closed Principle in c#.let understand with example.

public class DiscountService
    {
        private readonly IDiscountStrategy _discountStrategy;
        public DiscountService(IDiscountStrategy discountStrategy)
        {
            _discountStrategy = discountStrategy;
        }
        public double GetDiscountAmount(Order order)
        {
            return _discountStrategy.GetDiscount(order);
        }
    }
    public interface IDiscountStrategy
    {
        double GetDiscount(Order order);
    }
    public class VipDiscountStrategy : IDiscountStrategy
    {
        public double GetDiscount(Order order) => order.Amount * 0.1;
    }
    public class RegularDiscountStrategy : IDiscountStrategy
    {
        public double GetDiscount(Order order) => order.Amount * 0.05;
    }
    IDiscountStrategy vipDiscount = new DiscountService();
    DiscountService discountService = new DiscountService(vipDiscount);
    discountService.GetDiscountAmount(order);

This example following Open/Closed principle rules because if any new discount type need to add no need to modify existing code in discountService.simply create new discount type class and call them.

3) Liskov Substitution Principle (LSP)

1) Objects of a superclass should be replaceable with objects of a subclass without affecting the program.
2) Derived class should extend the behaviour of superclass.

Example : Without LSP

public class Bird
    {
        public virtual void Fly() { }
    }
    public class Ostrich : Bird
    {
        public override void Fly()
        {
            throw new NotImplementedException("Ostriches cannot fly");
        }
    }

Example : With LSP

public abstract class Bird
    {
        public abstract void Move();
    }
    public class Sparrow : Bird
    {
        public override void Move() { /* Implement flying logic */ }
    }
    public class Ostrich : Bird
    {
        public override void Move() { /* Implement running logic */ }
    }

4) Interface Segregation Principle (ISP)

It states break down the complex interface into smaller interfaces,more specific interfaces that make the code more readable and clean.

Example : Before ISP

public interface IEmployee
{
    void Work();
    void TakeBreak();
    void AttendMeeting();
}

Example : After ISP

public interface IWorkable
    {
        void Work();
    }
    public interface IBreakable
    {
        void TakeBreak();
    }
    public interface IMeetable
    {
        void AttendMeeting();
    }
    public class Employee : IWorkable, IBreakable, IMeetable
    {
        public void Work() { /* Work logic */ }
        public void TakeBreak() { /* Break logic */ }
        public void AttendMeeting() { /* Meeting logic */ }
    }

5) Dependency Inversion Principle (DIP)

It states that high level module should not depend on low level module.both should depend on abstraction.high level module should be interface or abstract classes,while low level classes should implement these abstractions.

Example : Before DIP

public class EmailService
    {
        private SmtpClient _smtpClient;
        public EmailService()
        {
            _smtpClient = new SmtpClient();
        }
        public void SendEmail(string to, string subject, string body)
        {
            _smtpClient.Send(to, subject, body);
        }
    }

Example : After DIP

public interface IEmailSender
    {
        void SendEmail(string to, string subject, string body);
    }
    public class SmtpEmailSender : IEmailSender
    {
        private SmtpClient _smtpClient;
        public SmtpEmailSender(SmtpClient smtpClient)
        {
            _smtpClient = smtpClient;
        }
        public void SendEmail(string to, string subject, string body)
        {
            _smtpClient.Send(to, subject, body);
        }
    }
    public class NotificationService
    {
        private readonly IEmailSender _emailSender;
        public NotificationService(IEmailSender emailSender)
        {
            _emailSender = emailSender;
        }
        public void Notify(string to, string message)
        {
            _emailSender.SendEmail(to, "Notification", message);
        }
    }

Conclusion

By following the SOLID principles, developers can create more modular, flexible, and maintainable software. In .NET Core, these principles are especially useful because of the framework's built-in support for features like dependency injection and interface-based programming.

 

Comments

No comments have been added to this article.

Add Comment