Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SOLID architecture principles using simple C# examples

0.00/5 (No votes)
30 Jul 2017 2  
This is an alternative for SOLID architecture principles using simple C# examples

Introduction

I read an article about SOLID on codeproject by Shivprasad koirala (https://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp)  and I’d like to make some improvements. I like Shivprasad’s idea of keeping it simple stupid so I will use his code examples as well. In order to understand my article better read his first.

What is SOLID?

The what is quite simple. It’s an acronym where:-

  • S stands for SRP (Single responsibility principle
  • O stands for OCP (Open closed principle)
  • L stands for LSP (Liskov substitution principle)
  • I stands for ISP ( Interface segregation principle)
  • D stands for DIP ( Dependency inversion principle)

 

But why do we need this? Because business changes and so does software in order to serve business. It helps you making less errors, more flexibility and less efforts when doing the changes. I’ll make it clearer after each example.

Understanding “S”- SRP (Single responsibility principle)

Ok, let’s take a look at Shivprasad’s example

class Customer
    {
        public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());
            }
        }
    }

Shivprasad said that “The above customer class is doing things WHICH HE IS NOT SUPPOSED TO DO” and I totally agree. However, it’s not very clear to new programmers what is supposed to be. I used to write codes like that myself, at least when I was in college and a time after that. That type of codes came to my mind quite straightforward and naturally because it’s the logic to get the job done. In order to write codes that follows SRP you have to change the way you think. Instead of coding following “logic” now we follow “duties” (responsibilities). Now look at the above codes and we’ll dig deeper into it.

Firstly

catch (Exception ex)
{
    System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());
}

If the exception is thrown, this will log the exception to “Error.txt” at “C” drive. If there’s no other place in your program doing the same thing then that code is ok, no need for SRP. However, it’s unlikely that only the customer class logging the error. In that case you have to repeat

System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());

Everywhere. Then one day, due to some reasons (maybe security) the location of the file is required to change, for ex to “d:\Error.txt” then we will have to change all “c:\Error.txt” to “d:\Error.txt” in code. But there’s a risk a human mistake might occur such as a typo like “f:\Error.txt”. That’s why we have to test everything again and that takes time. So, it’s better to group all the code that does the log into a class and if change is required we do it in one place only. That one class is responsible for logging and any class that needs to log something will have a reference to the logging class, might be initiated by class constructor. That means our code will become

class FileLogger
    {
        public void Log(string error)
        {
            System.IO.File.WriteAllText(@"c:\Error.txt", error);
        }
    }
class Customer
    {
       private readonly FileLogger _logger;
       public Customer(FileLogger logger)
        {
         _logger = logger;
        }

       public void Add()
        {
            try
            {
                // Database code goes here
            }
            catch (Exception ex)
            {
                _logger.Log(ex.ToString());
            }
        }
    }

Now another class for ex Product needs to use the log, very easy

class Product
    {
       private readonly FileLogger _logger;
       public Product(FileLogger logger)
        {
         _logger = logger;
        }
       public void DeleteProduct()
        {
                // Deleteing code goes here
                _logger.Log("Successful deleted");
        }
    }

Now you can see the benefit of seeing things as duties(responsibilities). Back to our first example, the class has 3 duties:

  1. The Add
  2. The Log
  3. Error handling: the “try catch” block

Now our code improves like this, each class is responsible for its own duty

class Customer
    {
       public void Add()
        {
                // Database code goes here
        }
    }

class FileLogger
    {
        public void Log(string error)
        {
            System.IO.File.WriteAllText(@"c:\Error.txt", error);
        }
    }
class SimpleErrorHandler
    {
       public delegate void InpuFunction();
       private readonly InpuFunction _inputFunction;
       private readonly FileLogger _logger;    
       public SimpleErrorHandler(InpuFunction inputfunction, FileLogger logger)
        {
            _inputFunction = inputFunction;
            _logger = logger;
        }
       public void ErrorHandle()
        {
            try
            {
                _inputFunction();
            }
            catch (Exception ex)
            {
                _logger.Log(ex.ToString());
            }
        }
    }

Now we plug things together

var customer = new Customer();
var fileLogger = new FileLogger();
var customerWithErrorHandling = new SimpleErrorHandler(customer.Add,fileLogger);
customerWithErrorHandling.ErrorHandle();

If we have more unmanaged code, the SimpleErrorHandler handles them quite easy

class FileRead
    {
       public void ReadFile()
        {
                // file reading code goes here
        }
    }
var fileRead = new FileRead();
var fileLogger = new FileLogger();
var customerWithErrorHandling = new SimpleErrorHandler(fileRead.ReadFile,fileLogger);
customerWithErrorHandling.ErrorHandle();

Understanding “O” - Open closed principle

Open for extension, closed for modification. It normally takes a lot of time to make the code correct and bug free, so extending instead of touching those. Here’s Shivprasad’s examples

class Customer
{
        public virtual double getDiscount(double TotalSales)
        {
            return TotalSales;
        }
}
class SilverCustomer : Customer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 50;
        }
    }
class goldCustomer : SilverCustomer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 100;
        }
    }

I don’t like this code though it follows the open closed principle. Because besides the OCP there are other principles as well such as: favor composition over inheritance or don’t repeat yourself. Here we can see that the logic of getDiscount was repeated. Let say if we have another bronzeCustomer class that is different but has the same discount as silverCustomer then we’ll have 2 exactly the same getDiscount functions. Now let’s see the powerful of interface and composition

 

interface IDiscount
{
        double getDiscount(double TotalSales);
}

class Customer
{
        private readonly IDiscount _discount;
        private readonly string _customerClass;
        public string CustomerClass { get { return _customerClass; } }
        public Customer(string customerClass, IDiscount discount)
        {
            _customerClass = customerClass;
             _discount = discount;
        }
        public double getDiscount(double TotalSales)
        {
            return _discount.getDiscount(TotalSales);
        }
}
class GetFixedDiscount : IDiscount
{
       private readonly double _fixednumber;
       public GetFixedDiscount(double fixednumber)
       {
           _fixednumber = fixednumber;
       }
       public double getDiscount(double TotalSales)
       {
           return TotalSales - _fixednumber;
       }
}

No repeat in getDiscount function in gold, silver and bronze customer anymore

var getFixed100Discount = new GetFixedDiscount(100);
var getFixed50Discount = new GetFixedDiscount(50);
var goldCustomer = new Customer("Gold Class", getFixed100Discount);
var silverCustomer = new Customer("Silver Class", getFixed50Discount);
var bronzeCustomer = new Customer("Bronze Class", getFixed100Discount);

Understanding “L”- LSP (Liskov substitution principle)

What is LSP? You can easily find the answer by Googling it

"objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program"

It’s just a well-designed use of inheritance. But why do we need this? Let’s take a look into Shivprasad’s example

class Enquiry : Customer
    {
        public override double getDiscount(double TotalSales)
        {
            return base.getDiscount(TotalSales) - 5;
        }
        public override void Add()
        {
            throw new Exception("Not allowed");
        }
    }

And

List<Customer> Customers = new List<Customer>();
Customers.Add(new SilverCustomer());
Customers.Add(new goldCustomer());
Customers.Add(new Enquiry());
 foreach (Customer o in Customers)
 {
                o.Add();
 }
}

Then the exception will be thrown. However in real life you can make it work by letting the Add() function doing nothing. I think violating LSP isn’t a big deal for the one who writes the code but for the one who maintains it later. If you are a new hired worker, you will be very confused. With those nothing and no meaning functions around you won’t know if a function has a real meaning or not. So, you have to dig into every function within a class and that will take a lot of time.

 

You can follow Shivprasad if building the software from the beginning. If you are the maintainer that’s not a good idea to start with because it requires serious code refactoring. Instead of making an enquiry a customer you can simply “make an enquiry for a customer”

class Enquiry
{
       private readonly Customer _customer;
       public Enquiry(Customer customer)
       {
             _customer = customer;
       }
       public double getDiscount(double TotalSales)
       {
              return _customer.getDiscount(TotalSales);
       }
}

Understanding “I” - ISP (Interface Segregation principle)

Basically it’s just making your interface “slim” so that your code can be reused without being forced to implement unnecessary behavior, good for code reuse. Let’s take a look at Shivprasad’s example

interface IDatabase
{
        void Add(); // old client are happy with these.
        void Read(); // Added for new clients.
}

In my opinion, this is unrealistic. In fact, interfaces are kind of immutable. You change them, you’ll break all classes that implement them, no one would do something like that even the worst programmer. In real life, when maintaining someone else’s code, you see “fat” interface with working implementation

interface IDatabase
{
        void Add(); 
        void Read(); 
}

Now you need Database with only the read. The solution is that making your class to have a reference to IDatabase and use it but it would have been much better if we had 2 interfaces instead of 1.

Understanding “D”- Dependency inversion principle

One should depend upon abstraction not concreate. It adds a ton of flexibility to your code. As you can see the example in my ORP section I used

class Customer
{
        private readonly IDiscount _discount;
        public Customer(string customerClass, IDiscount discount)
        {
            _customerClass = customerClass;
            _discount = discount;
        }
}

This is called “Dependency injection”, one way of implementing DIP. You can change the actual discount class to a mock discount to test the other parts of your code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here