S: Single Responsibility Principle
The following code has a problem. “Automobile” class contains two different responsibilities: First is to take care of the car model, adding accessories, etc. and then there is the second responsibility: To sell/lease the car. This breaks SRP. These two responsibilities are separate.
public Interface ICarModels { } public class Automobile : ICarModels { string Color { get; set; } string Model { get; set; } string Year { get; set; } public void AddAccessory(string accessory) { // Code to Add Accessory } public void SellCar() { // Add code to sell car } public void LeaseCar() { // Add code to lease car } }
To fix this issue, we need to break up the Automobile class and use separate interfaces:
public Interface ICarModels { } public class Automobile : ICarModels { string Color { get; set; } string Model { get; set; } string Year { get; set; } public void AddAccessory(string accessory) { // Code to Add Accessory } } public Interface ICarSales { } public class CarSales : ICarSales { public void SellCar() { // Add code to sell car } public void LeaseCar() { // Add code to lease car } }
While designing your interfaces and classes think about responsibilities. What will modifications to class involve? Break classes up into their simplest forms...but not any simpler (as Einstein would say).
O: Open/Closed Principle
When requirements change and more types are added for processing, classes should be extensible enough so that they don't require modifications. New classes can be created and used for processing. In other words classes should be extensible. I call this the "If-Type" principle. If you have lots of if (type == ....) in your code, you need to break it up into separate class levels.
In this example we are trying to calculate the total price of car models in a dealership.
public class Mercedes { public double Cost { get; set; } } public class CostEstimation { public double Cost(Mercedes[] cars) { double cost = 0; foreach (var car in cars) { cost += car.Cost; } return cost; } }
But a dealership does not only carry Mercedes! this is where the class is not extensible anymore! What if we want to add up other car model costs as well?!
public class CostEstimation { public double Cost(object[] cars) { double cost = 0; foreach (var car in cars) { if (car is Mercedes) { Mercedes mercedes = (Mercedes) car; cost += mercedes.cost; } else if (car is Volkswagen) { Volkswagen volks = (Volkswagen)car; cost += volks.cost; } } return cost; } }
It's now broken! for every car model in the dealership lot we must Modify the class and add another if statement!
So let's fix it:
public abstract class Car { public abstract double Cost(); } public class Mercedes : Car { public double Cost { get; set; } public override double Cost() { return Cost * 1.2; } } public class BMW : Car { public double Cost { get; set; } public override double Cost() { return Cost * 1.4; } } public class Volkswagen : Car { public double Cost { get; set; } public override double Cost() { return Cost * 1.8; } } public class CostEstimation { public double Cost(Car[] cars) { double cost = 0; foreach (var car in cars) { cost += car.Cost(); } return cost; } }
Here the problem is solved!
L: Liskov Substitution Principle
The L in SOLID refers to Liskov principle. The inheritance concept of Object Oriented programming can be solidified where derived classes cannot modify behavior of base classes in any manner. I will come back to a real world example of LISKOV Principle. But for now this is the principle itself:
T -> Base
where as T [the derived class] should not be tampering with behavior of Base.
I: Interface Segragation Principle
Interfaces in c# lay out methods that will need to be implemented by classes that implement the interface. For example:
Interface IAutomobile { public void SellCar(); public void BuyCar(); public void LeaseCar(); public void DriveCar(); public void StopCar(); }
Within this interface there are two groups of activities going on. One group belongs to a salesman and another belongs to a driver:
public class Salesman : IAutomobile { // Group 1: Sales activities that belong to a salesman public void SellCar() { /* Code to Sell car */ } public void BuyCar(); { /* Code to Buy car */ } public void LeaseCar(); { /* Code to lease car */ } // Group 2: Driving activities that belong to a driver public void DriveCar() { /* no action needed for a salesman */ } public void StopCar(); { /* no action needed for a salesman */ } }
In the above class we are forced to implement DriveCar and StopCar methods. Things that don't make sense for a salesman and do not belong there.
public class Driver : IAutomobile { // Group 1: Sales activities that belong to a salesman public void SellCar() { /* no action needed for a driver */ } public void BuyCar(); { /* no action needed for a driver */ } public void LeaseCar(); { /* no action needed for a driver */ } // Group 2: Driving activities that belong to a driver public void DriveCar() { /* actions to drive car */ } public void StopCar(); { /* actions to stop car */ } }
The same way we are now forced to implement SellCar, BuyCar and LeaseCar. Activities that clearly do not belong in Driver class.
To fix this issue we need to break up the interface into two pieces:
Interface ISales { public void SellCar(); public void BuyCar(); public void LeaseCar(); } Interface IDrive { public void DriveCar(); public void StopCar(); } public class Salesman : ISales { public void SellCar() { /* Code to Sell car */ } public void BuyCar(); { /* Code to Buy car */ } public void LeaseCar(); { /* Code to lease car */ } } public class Driver : IDrive { public void DriveCar() { /* actions to drive car */ } public void StopCar(); { /* actions to stop car */ } }
Segregation of Interfaces!
D : Dependency Inversion Principle
The question is: Who depends on who?
Let's say we have a traditional multi-layer application:
Controller Layer -> Business Layer -> Data Layer.
Assume from the Controller we want to tell the Business to save an Employee into the database. The Business Layer asks the Data Layer to perform this.
So we set out to create our Controller (MVC example):
public class HomeController : Controller { public void SaveEmployee() { Employee empl = new Employee(); empl.FirstName = "John"; empl.LastName = "Doe"; empl.EmployeeId = 247854; Business myBus = new Business(); myBus.SaveEmployee(empl); } } public class Employee { string FirstName { get; set; } string LastName { get; set; } int EmployeeId { get; set; } }
Then in our Business Layer we have:
public class Business { public void SaveEmployee(Employee empl) { Data myData = new Data(); myData.SaveEmployee(empl); } }
and in our Data Layer we create the connection and save the employee into the database. This is our traditional 3-Layer architecture.
Let's now make an improvement to our Controller. Instead of having SaveEmployee method right inside our controller, we can create a class that takes care of all Employee actions:
public class PersistPeople { Employee empl; // Constructor PersistPeople(Employee employee) { empl = employee; } public void SaveEmployee() { Business myBus = new Business(); myBus.SaveEmployee(); } public Employee RetrieveEmployee() { } public void RemoveEmployee() { } } // Now our HomeController is a bit more organized. public class HomeController : Controller { Employee empl = new Employee(); empl.FirstName = "John"; empl.LastName = "Doe"; empl.EmployeeId = 247854; PersistPeople persist = new Persist(empl); persist.SaveEmployee(); } }
Now let's concentrate on the PersistPeople class. It is hard-coded with and tightly coupled with the Employee class. It takes in an Emloyee in the contstructor and instantiates a Business class to save it. What if we want to save an "Admin" instead of "Employee"? Right now our Persist class is totally "Dependent" on the Employee class.
Let's use "Dependency Inversion" to solve this problem. But before doing that we need to create an interface that both Employee and Admin classes derive from:
Interface IPerson { string FirstName { get; set; } string LastName { get; set; } int EmployeeId { get; set; } } public class Employee : IPerson { int EmployeeId; } public class Admin : IPerson { int AdminId; } public class PersistPeople { IPerson person; // Constructor PersistPeople(IPerson person) { this.person = person; } public void SavePerson() { person.Save(); } } // Now our HomeController is using dependency inversion: public class HomeController : Controller { // If we want to save an employee we can use Persist class: Employee empl = new Employee(); empl.FirstName = "John"; empl.LastName = "Doe"; empl.EmployeeId = 247854; PersistPeople persist = new Persist(empl); persist.SavePerson(); // Or if we want to save an admin we can use Persist class: Admin admin = new Admin(); admin.FirstName = "David"; admin.LastName = "Borax"; admin.EmployeeId = 999888; PersistPeople persist = new Persist(admin); persist.SavePerson(); } }
So in summary our Persist class is not dependent and hard-coded to Employee class. It can take any number of types like Employee, Admin, etc. The control to save whatever is passed in now lies with the Persist class and not the HomeController. The Persist class now knows how to save whatever is passed in (Employee, Admin, etc.). Control is now inverted and given to Persist class. You can also refer to this blog for some great examples of SOLID principles:
Reference: https://darkwareblog.wordpress.com/2017/10/17/
I hope this helps!