We all write code with some patterns even when we dont realise it. I am trying to really understand some of the S.O.L.I.D principles and how you apply these principles in the real world.
I am struggling with "D".
I sometimes confuse Dependency Inversion with Dependency Injection. Does it mean that as long as you keep things depending on abstraction (IE:interfaces) you are done.
Does anybody have even a small C# example that explains it?
Thanks.
SOLID Principles is a coding standard that all developers should have a clear concept for developing software properly to avoid a bad design. It was promoted by Robert C Martin and is used across the object-oriented design spectrum. When applied properly it makes your code more extendable, logical, and easier to read.
According to Orner, while the practice of software development has changed in the past 20 years, SOLID principles are still the basis of good design. The author explains how they also apply to functional programming and microservices architecture, with examples.
Have a look at Mark Seeman's blog or, even better, buy his book. It covers so much more than just DI. I appreciate you probably just want a simple sample to get going with. However, it's a subject that many who claim to understand don't and therefore worth learning well.
That said, here's a very simple example. The terminology, as I understand it, is
Inversion of Control and Dependency Injection. Inversion of Control refers to the fact that you give control of a class's dependencies to some other class, opposed to the class controlling the dependency itself, usually via the new
keyword. This control is exerted via Dependency Injection where a class is given, or injected, with its dependencies. This can be done via an IoC framework or in code (known as Pure DI). Injection can be performed in the class's constructor, via a property or as a method's parameter. Dependencies can be any type, they don't have to be abstract.
Here's a class that lists Tour de France winners who haven't doped:
class CleanRiders
{
List<Rider> GetCleanRiders()
{
var riderRepository = new MsSqlRiderRepository();
return riderRepository.GetRiders.Where(x => x.Doping == false);
}
}
This class is dependent on the MsSqlRiderRepository
. The class takes control of the creation of the instance. The problem is that this dependency is inflexible. It's hard to change it to a OracleRiderRepository
or a TestRiderRepository
.
IoC and DI solve this for us:
class CleanRiders
{
private IRiderRepository _repository;
public CleanRiders(IRiderRepository repository)
{
_repository = repository;
}
List<Rider> GetCleanRiders()
{
return _repository.GetRiders.Where(x => x.Doping == false);
}
}
Now the class is only depending on an Interface. Control of the dependency has been given up to the class's creator and must be injected via its constructor:
void Main()
{
var c = new CleanRiders(new MsSqlRepository());
var riders = c.GetRiders();
}
Arguably, a more flexible, testable and SOLID approach.
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!
I was trying to explain this to my co-worker the other day and in the process I actually even understood the concept myself. Especially when I came up with the real-life example of dependency inversion in real life.
The story
Imagine if a car driver was dependent on a car: can only drive 1 car - THE car! This would be pretty bad:
In this case the direction of the dependency is: Driver => Car (the Driver object depends on the Car object).
Thankfully in real life each car has the interface: "steering wheel, pedals and gear shifter". A driver no longer depends on THE car, so a driver can drive ANY car:
Now TheDriver depends on the ICar interface, TheCar also depends on ICar interface - dependency is INVERTED:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With