You are on page 1of 8

What are the SOLID principles in C#?

SOLID is an acronym for the five object-oriented design principles. These principles enable
developers to develop software that is easily extendable and maintainable. SOLID
principles also ensure that software can easily adapt to changing requirements. Let’s look
at each of the principles, one by one:

Single responsibility principle (SRP)

SRP states that every class must have only one reason to change it. In other words, each
class must be responsible for a single purpose only. Consider the following code snippet,
featuring a User class with an update method:

class User
{
    void update(Database db, string msg)
    {
        try
        {
            db.Add(msg); // Update the DB
        }
        catch (Exception ex) // Catch and log error.
        {
            File.WriteAllText("errors.txt", ex.ToString());
        }
    }
}

The User class is not only updating the database, its also handling logging of errors.

Consider the SRP compliant code below:


class User
{
    private ErrorLogger error = new ErrorLogger();
    void update(Database db, string msg)
    {
        try
        {
            db.Add(msg);
        }
        catch (Exception ex)
        {
            error.handle(ex.ToString());
        }
    }
}
class ErrorLogger
{
    void handle(string error)
    {
      File.WriteAllText("errors.txt", error);
    }
}

Class User's responsibility is to update the database. Class ErrorHandler logs errors.
Open closed principle (OCP)
OCP states that classes must be open for extension, but closed for modification:

class TextMessage
{
    void sendMsg(Database db, string msg)
    {
        if (msg.StartsWith("@"))
        {
            db.Tag(msg); // messages starting with @ are tags.
        }
        // if another message that starts with '#' needs,
        // to be handled, then another if condition will be needed.
        // The class will need to be heavily modified to
        // incorporate changing requirements.
        else
        {
            db.Add(msg); // otherwise they're just simple messages.
        }
    }
}

In the above snippet, the TextMessage class has a sendMsg module. This module behaves


differently when the message starts with @.

However, if another type of message, that starts with # needs to be handled, then the class
will need to be modified by adding another if statement.
Consider the code below; it uses inheritance to comply with the Open-Closed Principle:

class TextMessage
{
    void sendMsg(Database db, string msg)
    {
        db.Add(msg);
    }
}
// A class made specifically for tagging. 
// If a new feature needs to be incorporated
class Tag : TextMessage // class Tag inherits TextMessage
{
    override void sendMsg(Database db, string msg)
    {
        db.Tag(msg);
    }
}

Liskov substitution principle (LSP)


LSP states that if SS is a subtype of TT, then objects of type TT may be replaced with
objects of type SS. Consider the example below:

class Bird
{
void fly()
{
System.Console.WriteLine("I believe I can fly!");
}
}

class Dove : Bird {}


class Ostrich : Bird {}

The Dove class is a subtype of Bird; objects of Bird can be replaced by objects


of Dove because doves can fly. However, the same cannot be done with objects
of Ostrich since they cannot fly. This is a violation of LSP.

Now consider the LSP compliant code below:

class Bird
{
  // functions that all birds do
}
class FlyingBirds : Bird 
{
  void fly()
  {
    System.Console.WriteLine("I believe I can fly!");   
  }
}
class Dove : FlyingBirds {}
class Ostrich : Bird {}

Objects of FlyingBirds are replaceable by Dove, and objects of simple Bird are replaceable


by objects of Ostrich; hence the code follows LSP.
Interface segregation principle (ISP)

ISP suggests that several specific client interfaces are better than having one general
interface. In other words, it is better to have multiple interfaces instead of adding lots of
functionality to one interface.

Consider the example below:

interface phoneTasks
{
  void call();
  void text();
  void games();
  void torch();
}
class smartPhone : phoneTasks
{
  public void call(){}
  public void text(){}
  public void games(){}
  public void torch(){}
}
class dumbPhone: phoneTasks
{
  public void call(){}
  public void text(){}
  // But the dumb phone does not have games, or a torch.
  // This is an ISP violation.
  public void games(){}
  public void torch(){}
}

The interface phoneTasks has methods for tasks that a smartphone can perform; however,
another phone may not have all of these features but is still implementing them.
Now consider the ISP compliant code below:

// Lots of specific interfaces is better than
// one big general interface.
interface phoneTasks
{
  void call();
  void text();
}
interface Games
{
  void games();
}
interface Torch
{
  void torch();
}
class smartPhone : phoneTasks, Games, Torch
{
  public void call(){}
  public void text(){}
  public void games(){}
  public void torch(){}
}
// dumbPhone is only implementing the features it has.
// no unneccessary interfaces.
class dumbPhone: phoneTasks
{
  public void call(){}
  public void text(){}
}

Dependency inversion principle (DIP)


DIP states that:

1. High-level modules should not depend on low-level modules. Both should depend on
abstractions.
2. Abstractions should not depend on details. Details should depend on abstractions.

Consider an example of a calculator class that is dependent on two low-level classes that
define its features. Now, if more features are to be added, then the calculator class would
need to be modified.

DIP dictates that high-level classes must not be dependent on low-level classes. In this case,
the calculator class should not be bogged down with the details of the implementation of its
features. The illustration below highlights this idea:

You might also like