Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Thursday, September 11, 2025

SOLID Principles in .NET and Java with Real Examples

 

SOLID Principles in .NET and Java with Real Examples

In software engineering, writing maintainable, scalable, and flexible code is crucial for long-term success. The SOLID principles, coined by Robert C. Martin, provide a foundational framework for object-oriented design to achieve just that. SOLID is an acronym for five key principles: Single Responsibility Principle (SRP), Open-Closed Principle (OCP), Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). These principles help developers avoid code rot, reduce coupling, and enhance extensibility.

This post explores each SOLID principle in depth, with practical coding examples in both Java and .NET (using C#). We'll include step-by-step breakdowns, real-life analogies, code snippets, and discussions on real-world usage in business contexts. By applying SOLID, teams can build systems that are easier to test, debug, and evolve—essential in fast-paced industries like e-commerce, finance, and cloud services.

Single Responsibility Principle (SRP)

Definition and Benefits

The Single Responsibility Principle states that a class should have only one reason to change, meaning it should focus on a single responsibility or task. This promotes modularity, making code easier to understand, test, and maintain. Benefits include fewer bugs from isolated changes, lower coupling, and simpler refactoring.

Real-Life Analogy

Imagine a restaurant where the chef handles cooking, serving, and billing. If the billing process changes (e.g., new tax laws), it affects the chef's core job. Instead, assign cooking to the chef, serving to waitstaff, and billing to the cashier—each has one responsibility.

Step-by-Step Example in Java

In an online food ordering system, avoid combining order placement, invoice generation, and notifications in one class.

  1. Violating SRP: A single class handles multiple tasks.
    java
    public class OrderService {
        public void placeOrder() { /* Order logic */ }
        public void generateInvoice() { /* Invoice logic */ }
        public void sendConfirmationEmail() { /* Email logic */ }
    }
  2. Adhering to SRP: Split into focused classes.
    java
    public class OrderService {
        public void placeOrder() { /* Order logic */ }
    }
    
    public class InvoiceService {
        public void generateInvoice() { /* Invoice logic */ }
    }
    
    public class NotificationService {
        public void sendConfirmationEmail() { /* Email logic */ }
    }
  3. Usage: Create instances and call methods separately for modularity.
    java
    OrderService orderService = new OrderService();
    orderService.placeOrder();
    InvoiceService invoiceService = new InvoiceService();
    invoiceService.generateInvoice();

This separation allows independent updates, like changing email providers without touching order logic.

Step-by-Step Example in .NET (C#)

For a user registration system, don't mix user data with registration and email logic.

  1. Violating SRP: One class does everything.
    csharp
    public class User {
        public string Username { get; set; }
        public string Email { get; set; }
    
        public void Register() {
            // Save to DB
            EmailSender emailSender = new EmailSender();
            emailSender.SendEmail("Welcome!", Email);
        }
    }
    
    public class EmailSender {
        public void SendEmail(string message, string recipient) {
            Console.WriteLine($"Sending: {message} to {recipient}");
        }
    }
  2. Adhering to SRP: Separate concerns.
    csharp
    public class User {
        public string Username { get; set; }
        public string Email { get; set; }
    }
    
    public class EmailSender {
        public void SendEmail(string message, string recipient) {
            Console.WriteLine($"Sending: {message} to {recipient}");
        }
    }
    
    public class UserService {
        public void RegisterUser(User user) {
            // Save to DB
            EmailSender emailSender = new EmailSender();
            emailSender.SendEmail("Welcome!", user.Email);
        }
    }
  3. Usage: Instantiate and use.
    csharp
    User user = new User { Username = "john", Email = "john@example.com" };
    UserService service = new UserService();
    service.RegisterUser(user);

This makes testing user data independent of email logic.

Real-Life Usage in Business

In e-commerce platforms like Amazon, SRP ensures services like inventory management don't handle payment processing, allowing teams to update features independently. Businesses report reduced downtime and faster deployments, with studies showing 20-30% improvement in maintenance efficiency.

Open-Closed Principle (OCP)

Definition and Benefits

Software entities should be open for extension but closed for modification. Use abstraction (interfaces/abstract classes) to add features without altering existing code. Benefits: Reduces regression bugs, promotes reuse, and supports scalability.

Real-Life Analogy

A transportation company adds electric vehicles without redesigning the entire fleet management system—just extends the vehicle types.

Step-by-Step Example in Java

For payment methods in a food ordering app.

  1. Violating OCP: Conditional logic requires changes for new methods.
    java
    public class PaymentService {
        public void pay(String method, double amount) {
            if (method.equals("CARD")) { /* Card logic */ }
            else if (method.equals("UPI")) { /* UPI logic */ }
        }
    }
  2. Adhering to OCP: Use interface for extension.
    java
    public interface PaymentMethod {
        void pay(double amount);
    }
    
    public class CardPayment implements PaymentMethod {
        public void pay(double amount) { /* Card logic */ }
    }
    
    public class UpiPayment implements PaymentMethod {
        public void pay(double amount) { /* UPI logic */ }
    }
    
    public class PaymentService {
        public void processPayment(PaymentMethod method, double amount) {
            method.pay(amount);
        }
    }
  3. Usage: Add new methods like WalletPayment without modifying PaymentService.
    java
    PaymentService service = new PaymentService();
    service.processPayment(new CardPayment(), 100.0);

Step-by-Step Example in .NET (C#)

For shape area calculations.

  1. Violating OCP: Switch statement needs updates for new shapes.
    csharp
    public enum ShapeType { Circle, Rectangle }
    
    public class Shape {
        public ShapeType Type { get; set; }
        public double Radius { get; set; }
        public double Length { get; set; }
        public double Width { get; set; }
    
        public double CalculateArea() {
            switch (Type) {
                case ShapeType.Circle: return Math.PI * Radius * Radius;
                case ShapeType.Rectangle: return Length * Width;
                default: throw new InvalidOperationException();
            }
        }
    }
  2. Adhering to OCP: Abstract class for extension.
    csharp
    public abstract class Shape {
        public abstract double CalculateArea();
    }
    
    public class Circle : Shape {
        public double Radius { get; set; }
        public override double CalculateArea() => Math.PI * Radius * Radius;
    }
    
    public class Rectangle : Shape {
        public double Length { get; set; }
        public double Width { get; set; }
        public override double CalculateArea() => Length * Width;
    }
  3. Usage: Extend with new shapes like Triangle.
    csharp
    Shape circle = new Circle { Radius = 5 };
    Console.WriteLine(circle.CalculateArea());

Real-Life Usage in Business

Netflix uses OCP in its recommendation engine, extending algorithms for new content types without rewriting core logic. This enables rapid innovation, reducing development costs in dynamic markets like streaming.

Liskov Substitution Principle (LSP)

Definition and Benefits

Subtypes must be substitutable for their base types without altering program correctness. This ensures inheritance hierarchies are logical. Benefits: Prevents unexpected behavior, improves polymorphism reliability.

Real-Life Analogy

Replacing a bird with a sparrow (both fly) works, but an ostrich (doesn't fly) breaks expectations in a flying simulation.

Step-by-Step Example in Java

For delivery modes in food ordering.

  1. Violating LSP: Subclass throws exceptions.
    java
    public class DeliveryVehicle {
        public void startEngine() { /* Engine logic */ }
    }
    
    public class Bicycle extends DeliveryVehicle {
        public void startEngine() {
            throw new UnsupportedOperationException("No engine");
        }
    }
  2. Adhering to LSP: Use abstract class with consistent method.
    java
    public abstract class DeliveryMode {
        public abstract void deliver();
    }
    
    public class BikeDelivery extends DeliveryMode {
        public void deliver() { System.out.println("Bike delivery"); }
    }
    
    public class ScooterDelivery extends DeliveryMode {
        public void deliver() { System.out.println("Scooter delivery"); }
    }
  3. Usage: Substitute freely.
    java
    DeliveryMode mode = new BikeDelivery();
    mode.deliver();

Step-by-Step Example in .NET (C#)

For shapes.

  1. Violating LSP: Square forces equal sides, breaking rectangle behavior.
    csharp
    public abstract class Shape {
        public abstract double Area { get; }
    }
    
    public class Rectangle : Shape {
        public virtual double Width { get; set; }
        public virtual double Height { get; set; }
        public override double Area => Width * Height;
    }
    
    public class Square : Rectangle {
        public override double Width { set => base.Width = base.Height = value; }
        public override double Height { set => base.Height = base.Width = value; }
    }
  2. Adhering to LSP: Separate classes without forced overrides.
    csharp
    public abstract class Shape {
        public abstract double Area { get; }
    }
    
    public class Rectangle : Shape {
        public double Width { get; set; }
        public double Height { get; set; }
        public override double Area => Width * Height;
    }
    
    public class Square : Shape {
        public double Side { get; set; }
        public override double Area => Side * Side;
    }
  3. Usage: Use base type.
    csharp
    Shape rect = new Rectangle { Width = 4, Height = 5 };
    Console.WriteLine(rect.Area);

Real-Life Usage in Business

In banking software, LSP ensures loan types (personal, mortgage) can substitute base Loan without breaking calculations. Firms like JPMorgan use this for reliable financial models, minimizing errors in high-stakes environments.

Interface Segregation Principle (ISP)

Definition and Benefits

Clients shouldn't depend on interfaces they don't use—prefer many small interfaces over one large one. Benefits: Reduces unnecessary implementations, improves flexibility.

Real-Life Analogy

A robot worker shouldn't implement "eat" like a human; separate interfaces for work and maintenance.

Step-by-Step Example in Java

For restaurant partners in food ordering.

  1. Violating ISP: Broad interface forces irrelevant methods.
    java
    public interface RestaurantPartner {
        void provideMenu();
        void provideNutritionalInfo();
        void handleOrders();
    }
  2. Adhering to ISP: Split interfaces.
    java
    public interface MenuProvider {
        void provideMenu();
    }
    
    public interface NutritionalInfoProvider {
        void provideNutritionalInfo();
    }
    
    public interface OrderHandler {
        void handleOrders();
    }
    
    public class Diner implements MenuProvider, OrderHandler {
        public void provideMenu() { /* Menu */ }
        public void handleOrders() { /* Orders */ }
    }
  3. Usage: Implement only needed.
    java
    Diner diner = new Diner();
    diner.provideMenu();

Step-by-Step Example in .NET (C#)

For worker interfaces.

  1. Violating ISP: One interface with unused methods.
    csharp
    public interface IWorker {
        void Work();
        void Eat();
    }
    
    public class Robot : IWorker {
        public void Work() { /* Work */ }
        public void Eat() { throw new NotImplementedException(); }
    }
  2. Adhering to ISP: Segregated interfaces.
    csharp
    public interface IWorkable {
        void Work();
    }
    
    public interface IEatable {
        void Eat();
    }
    
    public class Human : IWorkable, IEatable {
        public void Work() { /* Work */ }
        public void Eat() { /* Eat */ }
    }
    
    public class Robot : IWorkable {
        public void Work() { /* Work */ }
    }
  3. Usage: Selective implementation.
    csharp
    IWorkable robot = new Robot();
    robot.Work();

Real-Life Usage in Business

In cloud services like AWS, ISP allows APIs to implement only relevant interfaces (e.g., storage without compute), enabling modular integrations. This cuts development time for enterprises customizing services.

Dependency Inversion Principle (DIP)

Definition and Benefits

High-level modules shouldn't depend on low-level ones; both should depend on abstractions. Use dependency injection for loose coupling. Benefits: Easier testing (mocks), flexibility in swapping implementations.

Real-Life Analogy

A lamp (high-level) depends on a switch abstraction, not a specific bulb type—swap bulbs without rewiring.

Step-by-Step Example in Java

For logging in a service.

  1. Violating DIP: Direct dependency.
    java
    public class UserService {
        private FileLogger logger = new FileLogger();
        public void register() { logger.log("Registered"); }
    }
    
    public class FileLogger {
        public void log(String message) { /* File log */ }
    }
  2. Adhering to DIP: Use interface and injection.
    java
    public interface Logger {
        void log(String message);
    }
    
    public class FileLogger implements Logger {
        public void log(String message) { /* File log */ }
    }
    
    public class ConsoleLogger implements Logger {
        public void log(String message) { System.out.println(message); }
    }
    
    public class UserService {
        private Logger logger;
        public UserService(Logger logger) { this.logger = logger; }
        public void register() { logger.log("Registered"); }
    }
  3. Usage: Inject dependency.
    java
    UserService service = new UserService(new ConsoleLogger());
    service.register();

Step-by-Step Example in .NET (C#)

For data access in a repository.

  1. Violating DIP: Tight coupling.
    csharp
    public class UserRepository {
        private SqlDatabase db = new SqlDatabase();
        public void Save() { db.Save(); }
    }
    
    public class SqlDatabase {
        public void Save() { /* SQL save */ }
    }
  2. Adhering to DIP: Interface and injection.
    csharp
    public interface IDatabase {
        void Save();
    }
    
    public class SqlDatabase : IDatabase {
        public void Save() { /* SQL save */ }
    }
    
    public class MongoDatabase : IDatabase {
        public void Save() { /* Mongo save */ }
    }
    
    public class UserRepository {
        private IDatabase db;
        public UserRepository(IDatabase db) { this.db = db; }
        public void Save() { db.Save(); }
    }
  3. Usage: Constructor injection.
    csharp
    UserRepository repo = new UserRepository(new SqlDatabase());
    repo.Save();

Real-Life Usage in Business

Spring Boot in Java and ASP.NET Core use DIP for dependency injection, allowing banks like HSBC to swap databases (SQL to NoSQL) without code overhauls. This supports agile practices, cutting migration costs by up to 40%.

Conclusion

SOLID principles are timeless best practices for building robust software in .NET and Java. While they add initial design effort, they pay off in maintainability and scalability for businesses. Start small—apply one principle to a module—and evolve. In real-world scenarios, companies like Google and Microsoft embed SOLID in their architectures for efficient, error-resistant systems. Assess your codebase today and refactor for SOLID compliance to future-proof your applications.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here