Lets do it practically and see what each principle means and how it can be achieved.

To download the complete source code, please click here: Download Sourcecode

 1. SRP: Here I have aclass name Journal which has Add/Remove methods. But when I modified the class to add Save method, I break Single responsibility principle. In order to fix it, created another class which takes responsibility of persisting the objects.

public class Journal
   {
       private readonly List<string> entries = new List<string>();
 
       private static int count = 0;
 
       public int AddEntry(string text)
       {
           entries.Add($"{++count}{text}");
           return count; // memento pattern!
       }
 
       public void RemoveEntry(int index)
       {
           entries.RemoveAt(index);
       }
 
       public override string ToString()
       {
           return string.Join(Environment.NewLine, entries);
       }
 
       // breaks single responsibility principle
       public void Save(string filename, bool overwrite = false)
       {
           File.WriteAllText(filename, ToString());
       }
 
   }
 
   // handles the responsibility of persisting objects, 
hence fixes the single responsibility principle
   public class Persistence    {        public void SaveToFile(Journal journal, string filename
bool overwrite = false)        {            if (overwrite || !File.Exists(filename))            {                File.WriteAllText(filename, journal.ToString());            }        }    }

 2. OCP:  Here I have a class ProductFilter which filters Products based on Color or Size. Till here everything was working fine. But then new requirement came to filter both on Size and Color, hence breaks OCP. In order to fix that two new Interfaces are introduced and new filter classes are implementing those interfaces in to obey OCP. Now ProductFilter is closed for modification but extended with the help of interfaces.

public enum Color
    {
        White, Black, Blue, Gray
    }
 
    public enum Size
    {
        Small, Medium, Large, XLarge
    }
    public class Product
    {
        public string Name { getset; }
        public Color Color { getset; }
        public Size Size { getset; }
       
        public Product(string name, Color color, Size size)
        {
            Name = name ?? throw 
new ArgumentNullException(paramName: nameof(name));             Color = color;             Size = size;         }     }     public class ProductFilter     {         //During development, we were told to have Filter by Color 
or Size
        public IEnumerable<Product
FilterByColor(IEnumerable<Product> products, Color color)         {             foreach (var p in products)             {                 if (p.Color == color)                     yield return p;             }         }         public IEnumerable<Product
FilterBySize(IEnumerable<Product> products, Size size)         {             foreach (var p in products)             {                 if (p.Size == size)                     yield return p;             }         }         //But now new requirement came in, Client wants both 
filert by color & size
        //So in order to accomplish that, we need to implement 
both the color and size filters
        //It violates OCP which says Class must by open for extention
  but close for modification
        public static IEnumerable<Product
FilterBySizeAndColor(IEnumerable<Product> products, Size size
Color color)         {             foreach (var p in products)                 if (p.Size == size && p.Color == color)                     yield return p;         }     }     //In order to fix that we introduced two new interfaces that are 
open for extension
    public interface ISpecification<T>     {         bool IsSatisfied(Product p);     }     public interface IFilter<T>     {         IEnumerable<T> Filter(IEnumerable<T> items
ISpecification<T> spec);     }     public class ColorSpecification : ISpecification<Product>     {         private Color color;         public ColorSpecification(Color color)         {             this.color = color;         }         public bool IsSatisfied(Product p)         {             return p.Color == color;         }     }     public class SizeSpecification : ISpecification<Product>     {         private Size size;         public SizeSpecification(Size size)         {             this.size = size;         }         public bool IsSatisfied(Product p)         {             return p.Size == size;         }     }     // combinattion for both Color & Size     public class AndSpecification<T> : ISpecification<T>     {         private ISpecification<T> first, second;         public AndSpecification(ISpecification<T> first
ISpecification<T> second)         {             this.first = first ?? throw 

new ArgumentNullException(paramName: nameof(first));             this.second = second ?? throw 

new ArgumentNullException(paramName: nameof(second));         }         public bool IsSatisfied(Product p)         {             return first.IsSatisfied(p) && second.IsSatisfied(p);         }     }     public class BetterFilter : IFilter<Product>     {         public IEnumerable<Product> Filter(IEnumerable<Product> items
ISpecification<Product> spec)         {             foreach (var i in items)                 if (spec.IsSatisfied(i))                     yield return i;         }     }

 3. LSP: Here I have created Rectangle class and intentionally violated Liskov substitution principle. Another class Square is inheriting from Rectangle class but when I substituted the Rectangle class object with its sub-type Square, it returned unexpected result.

//While violating LSP
 
   public class Rectangle
   {
       public int Width { getset; }
       public int Height { getset; }
 
       public Rectangle()
       {
 
       }
 
       public Rectangle(int width, int height)
       {
           Width = width;
           Height = height;
       }
 
       public override string ToString()
       {
           return $"{nameof(Width)}{Width}{nameof(Height)}

{Height}";        }    }    public class Square : Rectangle    {        public new int Width        {            set { base.Width = base.Height = value; }        }        public new int Height        {            set { base.Width = base.Height = value; }        }    }

 In order to following LSP, I have modified the code as below. Just few changes which we are already familiar with. Modified the properties as virtual so that the behavior can be altered from base class to the derived class in order to make sure its running for the sub type as well. Code is self explainatory.

public class ModifiedRectangle
    {
        public virtual int Width { getset; }
        public virtual int Height { getset; }
 
        public ModifiedRectangle()
        {
 
        }
 
        public ModifiedRectangle(int width, int height)
        {
            Width = width;
            Height = height;
        }
 
        public override string ToString()
        {
            return $"{nameof(Width)}{Width}{nameof(Height)}

{Height}";         }     }     public class ModifiedSquare : ModifiedRectangle     {         public override int Width          {             set { base.Width = base.Height = value; }         }         public override int Height         {             set { base.Width = base.Height = value; }         }     }

 4. ISP : Here we have a Interface IMachine exposed to the outer world which has Print, Fax & Scan methods. So if any of the user is still using old fashioned printer which only has Print functionality, he also need to implement Fax and Scan methods although he doesn't need to do so. This violates the Iterface segreation principle. In order to fix that, instead of one big bulky interface we should provide seperate interfaces.

public class Document
    {
    }
 
    //Here we have Just one interface having multiple methods
    public interface IMachine
    {
        void Print(Document d);
        void Fax(Document d);
        void Scan(Document d);
    }
 
     //What if there are some clients which are having dot matrix 
old style printer
    //although they don't need Fax & Scan but still need to implement 
it as they are inherting from our interface
    //this violates ISP     public class OldFashionedPrinter : IMachine     {         public void Print(Document d)         {             Console.WriteLine("Print document");         }         public void Fax(Document d)         {             throw new System.NotImplementedException();         }         public void Scan(Document d)         {             throw new System.NotImplementedException();         }     }     //ISP says, instead of having one big interface, you should
 have smaller interfaces so that they are self contained
    public interface IPrinter     {         void Print(Document d);     }     public interface IScanner     {         void Scan(Document d);     }     public interface IFax     {         void Fax(Document d);     }     public class Printer : IPrinter     {         public void Print(Document d)         {             Console.WriteLine("Print document");         }     }     public class Scanner : IScanner     {         public void Scan(Document d)         {             Console.WriteLine("Scanner document");         }     }     public class Faxer : IFax     {         public void Fax(Document d)         {             Console.WriteLine("Fax document");         }     }     public interface IMultiFunctionDevice : IPrinterIScanner ,IFax     {     }     public class Photocopier : IPrinterIScanner     {         private IPrinter printer;         private IScanner scanner;         public Photocopier(IPrinter printer, IScanner scanner)         {             if (printer == null)             {                 throw new ArgumentNullException(paramName: 

nameof(printer));             }             if (scanner == null)             {                 throw new ArgumentNullException(paramName: 

nameof(scanner));             }             this.printer = printer;             this.scanner = scanner;         }         public void Print(Document d)         {             printer.Print(d);         }         public void Scan(Document d)         {             scanner.Scan(d);         }     }     public class MultiFunctionMachine : IMultiFunctionDevice     {         private IPrinter printer;         private IScanner scanner;         private IFax fax;         public MultiFunctionMachine(IPrinter printer, IScanner scanner
IFax fax)         {             if (printer == null)             {                 throw new 

ArgumentNullException(paramName: nameof(printer));             }             if (scanner == null)             {                 throw new 

ArgumentNullException(paramName: nameof(scanner));             }             if (fax == null)             {                 throw new 

ArgumentNullException(paramName: nameof(fax));             }             this.printer = printer;             this.scanner = scanner;             this.fax = fax;         }         public void Print(Document d)         {             printer.Print(d);         }         public void Scan(Document d)         {             scanner.Scan(d);         }         public void Fax(Document d)         {             fax.Fax(d);         }     }

 5. DIP : In below example, User class has delegated the dependent object creation to client consuming it, thus making the User class concentrate on what its intended to do.

//High-level modules should not depend on low-level modules. 
Both should depend on abstractions.
    //Abstractions should not depend upon details.
Details should depend upon abstractions.
    public interface IMessage     {         string SendMessage();     }     public class SendEmail : IMessage     {         public string SendMessage()         {             return "Mail Sent...";         }     }     public class SendSms : IMessage     {         public string SendMessage()         {             return "Sms Sent...";         }     }     public class User     {         public string From { getset; }         public string To { getset; }         public string Subject { getset; }         public string Content { getset; }         private IMessage message;         public User(IMessage _message)         {             message = _message;         }         public void Send()         {             switch (message.ToString())             {                 case "DIP.SendEmail":                     Console.WriteLine("Mail Sent...");                     break;                 case "DIP.SendSms":                     Console.WriteLine("Sms Sent...");                     break;             }         }     }

 Please share your feedback, let me know your views on it. Source code is open to be modfied on git. Go ahead. Lets learns and share knowledge with the world.

To download the complete source code, please click here: Download Sourcecode

blog comments powered by Disqus