Dot Net For All

SOLID Design principles with C# practical examples

SOLID Design Principles

Do you really want to develop the extensible, flexible and mobile code while working on the project in your organization. If yes then you should be aware of the SOLID design principles. In this article I will cover SOLID design principles with the help of practical and real world examples which we can relate to our own application.

To learn C# and face interview with confidence I have recommended some very good books. These books can really help you to understand .NET and C#. Below is the link:

Best books to Learn csharp(C#) programming

Before going further have you ever encountered this kind of conversation with your colleague in your organization

Developer 1: I am really worried.

Developer 2: What Happened

Developer 1: Whenever I fix a bug in the system, some other functionality brakes which I have not at all touched.

Developer 2: hmm..Have you used SOLID principles while designing and developing the application ?

Developer 1: No, what are they?

SOLID Principles

Some important point about SOLID design principles.

How SOLID principles Solves read world Application Problems

Some of the design smell I which I want to discuss here are.

  1. Rigidity – The design is difficult to change. Without proper use of abstraction and interface the design becomes very rigid. For each and every small change in the functionality we have to test whole logic from start.
  2. Fragility – Design is easy to break. As discussed with a small change there are very high chances of the whole design going for a toss.
  3. Immobility – The design is difficult to reuse. Or rather we cannot easily extend the current design.
  4. Viscosity – It is difficult to do the right thing.
  5. Needless Complexity – Over design.

Following are the five SOLID design principles which we should be ware of

S – Single responsibility principle(SRP)

O – Open closed principle(OCP)

L – Liskov Substitution principle(LSP). How polymorphism should work.

I – Interface segregation Principle(ISP). How interface should be designed.

D- Dependency inversion principle(DIP) which describes principle between abstraction and concrete types.

There is no built-in order to these principles. Just because Single responsibility come first does not mean that it is more important than any other principle in C#. All of the parts of the SOLID are equally important to be implemented in the code. It is like that just because it is a nice acronym. As seen in the below figure all the five principles are related to each other.

Robert C. martin the inventor of the Design principle initially had only three principles designed. But later someone added two more principle and it came out to be a nice acronym.

Apart from the order, one more thing which is important is the relationship among all these principle.

Single Responsibility Principle

I will discuss the example of the single responsible principle along with the Open closed principle next.

Open Closed Principle

Lets see an example of the C# shape class which is not using any of the design principles.

 public class Shape
    {
        //TODO add more shapes if needed
        public const int SQUARE = 1;
        public const int CIRCLE = 2;
        public const int EQUILATERAL_TRIANGLE = 3;
        private readonly double width;
        public int type = -1;

        public Shape(int type, double width)
        {
            this.type = type;
            this.width = width;
        }

        public double getArea()
        {
            switch (type)
            {
                case SQUARE:
                    return width * width;
                case CIRCLE:
                    return Math.PI * (width / 2) * (width / 2);
                case EQUILATERAL_TRIANGLE:
                    return (Math.Sqrt(3) / 4) * width * width;
            }
            throw new SystemException("Can`t compute area of unknown shape");
        }

        public static double CalculateTotalArea(List<Shape> shapes)
        {
            double totalArea = 0;
            foreach (var shape in shapes)
            {
                for (int i = 0; i < shapes.Count; i++)
                {
                    if (shapes[i].type == SQUARE)
                    {
                        totalArea += shapes[i].getArea();
                    }
                    if (shapes[i].type == CIRCLE)
                    {
                        totalArea += shapes[i].getArea();
                    }
                    if (shapes[i].type == EQUILATERAL_TRIANGLE)
                    {
                        totalArea += shapes[i].getArea();
                    }
                }
            }
            return totalArea;
        }
    }

Though there are many short coming in this class. But what is the basic shortcoming which you can see. For every shape added we have to change the GetArea() method as well as the CalculateTotalArea() method. There are many reason for the class to change. It means the class is violating the SRP.

Again whenever we have to add a new shape we have to add a new const as well as add a switch and if statement in both of the methods. It means that the class is not closed for modifications and not easily extend able.

Now lets redesign it. We have created a new system in C# language.

public interface IShape
   {
       int Width
       { get; set}
       double GetArea();
   }
   public class Sqaure : IShape
   {
       private int width; public Sqaure() { }
       public int Width
       {
           get
           { return width; }
           set { width = value; }
       }
       public double GetArea()
       {
           double area = Width * Width; return area;
       }
   }
   public class Circle : IShape
   {
       private int width; public Circle() { }
       public int Width { get { return width; } set { width = value; } }
       public double GetArea()
       { double area = Math.PI * (double)((double)Width / (double)2) * (double)((double)Width / (double)2); return area; }
   }
   public interface IAreaCalculator
   {
       double CalculateArea(IList<IShape> shapes);
   }
   public class AreaCalculator : IAreaCalculator
   {
       public double CalculateArea(IList<IShape> shapes)
       {
           double totalArea = 0;
           foreach (IShape shape in shapes) { totalArea += shape.GetArea(); }
           return totalArea;
       }
   }

In the above code I have redesigned the class and abstracted out the functions. Now we can see that each and every class is responsible for one and only one functionality. If we want to add a new Shape, we have to only derive from the IShape class and IAreaCalculator will do the needful of calculating the total area of all the shapes.

The above code is following the SRP as said. It also follows the ORP as there is no need to change the AreaCalcualator class whenever a new shape is added. At run time it will take care of all the implementations of the IShape interface.

Liskov Substitution Principle

Breaking the LSP:

C# LSP Violation
    ReadOnlyCollection<Person> readOnlyList = new ReadOnlyCollection<Person>(person);
    var newCollection = readOnlyList as ICollection<Person>;
    newCollection.Add(new Person() { Name = "New Pserson" });

Example.

    public interface IShape
    {
        int Width { get; set; }
        double GetArea();
        double GetVolume();
    }

    public class Sqaure : IShape
    {
        private int width;
        public Sqaure()
        {
        }

        public int Width
        {
            get { return width; }
            set { width = value; }
        }

        public double GetArea()
        {
            double area = Width * Width;
            return area;
        }

        public double GetVolume()
        {
            throw new NotImplementedException();
        }
    }

    public class Cube : IShape
    {
        private int width;
        public int Width
        {
            get { return width; }
            set { width = value; }
        }

        public double GetArea()
        {
            return 6 * Width * Width;
        }

        public double GetVolume()
        {
            return Width * Width * Width;
        }        
    }

    public interface IVolumeCalculator
    {
        double CalculateVolume(IList<IShape> shapes);
    }

    public class VolumeCalculator : IVolumeCalculator
    {
        public double CalculateVolume(IList<IShape> shapes)
        {
            double totalVolume = 0;
            foreach (var shape in shapes)
            {
                totalVolume += shape.GetVolume();
            }
            return totalVolume;
        }
    }

We have decided to add more shapes to our system. Please check the above C# code. But these shapes are three dimensional. These shapes  can have Volume apart from the area. I have added a new method to IShape interface named GetVolume.

I have created a new shape named Cube which derived from IShape. We have a new class named VolumeCalculator which calculates the total volume of all the shapes.

But since we have added new method to IShape interface, we have to implement the method to existing shapes. As I have done for Square, but it is not implemented.

Now if the collection which are sending to the VolumeCalculator contains any of the 2D shapes, it will throw a NotSupportedException for them. This is the violation of LSP in SOLID design principles.

Interface Segregation Principle(ISP)

   public interface IShape
    {
        int Width { get; set; }
    }

    public interface IVolume
    {
        double GetVolume();
    }

    public interface IArea
    {
        double GetArea();
    }

    public class Sqaure : IShape, IArea
   {
       private int width;
       public Sqaure()
       {
       }

       public int Width
       {
          get { return width; }
          set { width = value; }
       }

       public double GetArea()
      {
        double area = Width * Width;
        return area;
      }
   }

    public class Cube : IShape, IArea, IVolume
    {
        private int width;
        public int Width
        {
            get { return width; }
            set { width = value; }
        }

        public double GetArea()
        {
            return 6 * Width * Width;
        }

        public double GetVolume()
        {
            return Width * Width * Width;
        }        
    }

    public interface IVolumeCalculator
    {
        double CalculateVolume(IList<IVolume> shapes);
    }

    public class VolumeCalculator : IVolumeCalculator
    {
        public double CalculateVolume(IList<IVolume> shapes)
        {
            double totalVolume = 0;
            foreach (var shape in shapes)
            {
                totalVolume += shape.GetVolume();
            }
            return totalVolume;
        }
    }

The above C# code is self explanatory. You can notice how I have segregated the interfaces to achieve ISP.

Dependency Inversion Principle

The important points to keep in mind while developing the solutions which meet the criteria set by DIP are.

Now as per the design of the previous code suppose the VolumeCalculator class is also used to display a message as shown in the below code

    public class VolumeCalculator : IVolumeCalculator
    {
        Messanger messager;
        public VolumeCalculator()
        {
            messager = new Messanger();
        }

        public double CalculateVolume(IList<IVolume> shapes)
        {
            double totalVolume = 0;
            foreach (var shape in shapes)
            {
                totalVolume += shape.GetVolume();
            }

            messager.Message(totalVolume);

            return totalVolume;
        }
    }

    public class Messanger
    {
        public void Message(double volume)
        {
            Console.WriteLine("The total Volume is " + volume);
        }
    }

Now if some other client comes and they want the message to be displayed in Other Language e.g. French. Is our code extensible? No, not at all. This scenario can be handled by abstracting out the Message() functionality as shown in the below code.

  public class VolumeCalculator : IVolumeCalculator
    {
        IMessanger messager;
        public VolumeCalculator(IMessanger msg)
        {
            messager = msg;
        }

        public double CalculateVolume(IList<IVolume> shapes)
        {
            double totalVolume = 0;
            foreach (var shape in shapes)
            {
                totalVolume += shape.GetVolume();
            }

            messager.Message(totalVolume);

            return totalVolume;
        }
    }

    public interface IMessanger
    {
        void Message(double volume);
    }

    public class EnglishMessanger: IMessanger
    {
        public void Message(double volume)
        {
            Console.WriteLine("The total Volume is " + volume);
        }
    }

    public class FrenchMessanger : IMessanger
    {
        public void Message(double volume)
        {
            Console.WriteLine("Le volume total est " + volume);
        }
    }

In the above code VolumeCalculator class is determining at run time which instance to use.

Conclusion:

In this article I have discussed all the SOLID principles with practical C# examples. I hope this will help you to have a better understanding of SOLID principles which will eventually help you to code better.

References:

This is a very good course to learn SOLID Principles

Top career enhancing courses you can't miss

My Learning Resource

Excel your system design interview