Click here to Skip to main content
15,867,756 members
Articles / SOLID
Alternative
Article

SOLID architecture principles using simple C# examples

Rate me:
Please Sign up or sign in to vote.
3.69/5 (13 votes)
30 Jul 2017CPOL5 min read 19.5K   14  
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

C#
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

C#
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

C#
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

C#
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

C#
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

C#
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

C#
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

C#
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

 

C#
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

C#
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

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

And

C#
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”

C#
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

C#
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

C#
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

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Australia Australia
Passionate about programming and latest technology
Love ping pong

Comments and Discussions

 
-- There are no messages in this forum --