Tag: Design Principles

  • SOLID Coding Practices: An Interview Refresher

    SOLID Principles

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

    This post provides a concise summary of each principle with C# examples here and at https://github.com/dotnetdiva/SOLID to refresh your memory before technical interviews or design discussions.

    Software design principles assist developers to write systems that are modular, scalable, and easier to maintain. Among the most widely recognised are the SOLID principles, a set of five guidelines introduced by Robert C. Martin to promote high-quality object-oriented design.

    For interview preparation, understand each principle’s intent and be able to discuss an example of implementation. The expected output is shown in GitHub for the code samples, but the key to understanding is to walk through the code and see how it applies to each principle.


    1. Single Responsibility Principle (SRP)

    Definition

    A class should have only one reason to change, meaning it should perform a single, well-defined task.

    Example

    Consider a class that processes user data and writes it to a file. These are two distinct responsibilities. A better approach is to separate them:

    public class UserDataProcessor
    {
        public void Process(User user)
        {
            // logic
        }
    }
    
    public class FileWriter
    {
        public void SaveToFile(string data)
        {
            // logic
        }
    }

    Why it matters

    This separation improves maintainability and reduces the likelihood of introducing unintended side effects when modifications occur.


    2. Open/Closed Principle (OCP)

    Definition

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

    Example

    Instead of modifying an existing class to add new behaviour, use inheritance or composition to extend functionality:

    public interface IShape
    {
        double Area();
    }
    
    public class Rectangle : IShape
    {
        public double Width { get; set; }
        public double Height { get; set; }
        public double Area() => Width * Height;
    }
    
    public class Circle : IShape
    {
        public double Radius { get; set; }
        public double Area() => Math.PI * Radius * Radius;
    }

    Why it matters

    This principle supports flexibility and reduces the risk of regression when new features are introduced.


    3. Liskov Substitution Principle (LSP)

    Definition

    Objects of a derived class should be substitutable for objects of the base class without affecting program correctness.

    This concept was introduced by Barbara Liskov and Jeannette Wing (1994), who defined behavioural subtyping as a requirement that subtypes preserve correctness. Robert C. Martin later incorporated this into SOLID to ensure that inheritance models support predictable substitution.

    Example

    A violation of LSP occurs when a subclass contradicts the expectations set by the base class:

    public class Bird
    {
        public virtual void Fly() => Console.WriteLine("Flying");
    }
    
    public class Penguin : Bird
    {
        public override void Fly() =>
            throw new NotSupportedException("Penguins cannot fly");
    }

    In this example, substituting a Penguin where a Bird is expected breaks behaviour. A better design might separate FlyingBird and NonFlyingBird abstractions.


    4. Interface Segregation Principle (ISP)

    Definition

    Clients should not be forced to depend on interfaces they do not use.

    Example

    Large, general-purpose interfaces should be divided into smaller, role-specific ones:

    
    public interface IPrinter
    {
        void Print();
    }
    
    public interface IScanner
    {
        void Scan();
    }

    Why it matters

    This principle promotes cleaner, more modular interfaces and prevents unnecessary dependencies between unrelated functionalities.


    5. Dependency Inversion Principle (DIP)

    Definition

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

    Example

    A class should depend on an interface rather than a concrete implementation:

    
    public interface IMessageService
    {
        void Send(string message);
    }
    
    public class EmailService : IMessageService
    {
        public void Send(string message) =>
            Console.WriteLine($"Email: {message}");
    }
    
    public class Notification
    {
        private readonly IMessageService _service;
    
        public Notification(IMessageService service)
        {
            _service = service;
        }
    
        public void SendAlert(string message) =>
            _service.Send(message);
    }

    Why it matters

    This approach enhances testability, promotes loose coupling, and supports dependency injection — a core practice in modern software frameworks.


    Summary

    Understanding the SOLID principles strengthens your ability to design maintainable, scalable, and testable systems. During interviews, be prepared to:

    • Explain the intent of each principle
    • Provide a concise code example

    Sources

    1. Robert C. Martin, Design Principles and Design Patterns, Object Mentor (2000).
    2. Barbara Liskov & Jeannette Wing, A Behavioral Notion of Subtyping, ACM TOPLAS 16(6), 1811–1841 (1994). https://doi.org/10.1145/197320.197383