Mastering SOLID Principles in .NET: A Practical Example for Clean and Maintainable Code

Usman Iqbal
4 min readMar 5, 2023

--

SOLID is an acronym for five design principles that aim to make the software more maintainable, scalable, and easy to understand. The SOLID principles were introduced by Robert C. Martin (a.k.a. Uncle Bob) and are considered the foundation of modern software engineering, and they guide developers in writing clean and modular code.

SOLID PRINCIPLE — OOD

In this article, we’ll go through each of the five SOLID principles and implement an example in .NET using C#.

S — Single Responsibility Principle (SRP)
The Single Responsibility Principle states that a class should have only one responsibility. In other words, a class should do only one thing and do it well. This principle helps in making code more modular, easier to maintain, and easier to test.

Let’s consider an example of an online shopping system. We can have a class called Order that represents an order made by a customer. The Order class should only be responsible for handling order-related functionalities.

public class Order
{
public int OrderId { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public string CustomerEmail { get; set; }

public void PlaceOrder(List<OrderItem> items)
{
// Place the order
}

public List<Order> GetOrdersForCustomer(string customerEmail)
{
// Get orders for the customer
}
}

O — Open-Closed Principle (OCP)
The Open-Closed Principle states that classes should be open for extension but closed for modification. This principle ensures that existing code remains unchanged while new functionality can be added.

In our online shopping system, we can have a class called DiscountCalculator that calculates the discount on an order. The DiscountCalculator class should be open for extension, meaning that we can add new types of discounts without modifying the existing code.

public abstract class DiscountCalculator
{
public abstract decimal CalculateDiscount(Order order);
}

public class PercentageDiscount : DiscountCalculator
{
public decimal Percentage { get; set; }

public override decimal CalculateDiscount(Order order)
{
decimal discount = (order.TotalAmount * Percentage) / 100;
return discount;
}
}

public class FixedAmountDiscount : DiscountCalculator
{
public decimal Amount { get; set; }

public override decimal CalculateDiscount(Order order)
{
return Amount;
}
}

L — Liskov Substitution Principle (LSP)
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. This principle ensures that code is reusable and interchangeable.

In our online shopping system, we can have a class called PaymentMethod that represents a payment method used by a customer to pay for an order. The PaymentMethod class should be replaceable with its subclasses without affecting the correctness of the program.

public abstract class PaymentMethod
{
public abstract bool ProcessPayment(Order order);
}

public class CreditCardPayment : PaymentMethod
{
public override bool ProcessPayment(Order order)
{
// Process credit card payment
return true;
}
}

public class PayPalPayment : PaymentMethod
{
public override bool ProcessPayment(Order order)
{
// Process PayPal payment
return true;
}
}

I — Interface Segregation Principle (ISP)
The Interface Segregation Principle states that clients should not be forced to depend on interfaces they do not use. In other words, interfaces should be small and specific to the needs of the client. This principle ensures that code is easy to maintain and modify.

In our online shopping system, we can have an interface called IOrderRepository that represents a repository for storing and retrieving orders. The interface should only contain methods that are relevant to the client.

public interface IOrderRepository
{
void AddOrder(Order order);
Order GetOrderById(int orderId);
List<Order> GetOrdersByCustomer(string customerEmail);
}

D — Dependency Inversion Principle (DIP)
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules, but both should depend on abstractions. Abstractions should not depend on details, but details should depend on abstractions. This principle ensures that code is flexible and easy to modify.

In our online shopping system, we can have a class called OrderService that depends on the IOrderRepository interface instead of a specific implementation. This allows us to switch to a different implementation of the repository without affecting the OrderService class.

public class OrderService
{
private readonly IOrderRepository _orderRepository;

public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}

public void PlaceOrder(Order order, List<OrderItem> items)
{
order.PlaceOrder(items);
_orderRepository.AddOrder(order);
}

public Order GetOrderById(int orderId)
{
return _orderRepository.GetOrderById(orderId);
}

public List<Order> GetOrdersByCustomer(string customerEmail)
{
return _orderRepository.GetOrdersByCustomer(customerEmail);
}
}

Conclusion
In this article, we have discussed the SOLID principles and implemented an example in .NET using C# that demonstrates all five principles. By following these principles, we can create software that is modular, maintainable, and extensible. The SOLID principles are essential for modern software engineering and should be part of every developer’s toolkit.

Thank you for reading this article on SOLID principles. I hope that you found it informative and useful in your software development journey. For more related content you can follow me on Medium and LinkedIn. Don’t hesitate to reach out with any questions or comments!

--

--

Usman Iqbal

Computer Science enthusiast with a To-Do attitude, eager to work & produce quick results for mutual growth