SOLID
//In violation because Orange is not a kind of apple
namespace SolidDemo
{
class Program
{
5. static void Main(string[] args)
{
Apple apple = new Orange();
Console.WriteLine(apple.GetColor());
}
10. }
public class Apple
{
public virtual string GetColor()
15. {
return "Red";
}
}
20. public class Orange : Apple
{
public override string GetColor()
{
return "Orange";
25. }
}
}
//Fixed by having a generic base class for both apple and orange
namespace SolidDemo
{
class Program
{
5. static void Main(string[] args)
{
Fruit fruit = new Orange();
Console.WriteLine(fruit.GetColor());
fruit = new Apple();
10. Console.WriteLine(fruit.GetColor());
}
}
public abstract class Fruit
15. {
public abstract string GetColor();
}
public class Apple : Fruit
20. {
public override string GetColor()
{
return "Red";
}
25. }
public class Orange : Apple
{
public override string GetColor()
30. {
return "Orange";
}
}
}
//Violating SRP by mixing OpenGate and CloseGate into veh service
public class ServiceStation
{
public void OpenGate()
{
5. //Open the gate if the time is later than 9 AM
}
public void DoService(Vehicle vehicle)
{
10. //Check if service station is opened and then
//complete the vehicle service
}
public void CloseGate()
15. {
//Close the gate if the time has crossed 6PM
}
}
//Fix
public class ServiceStation
{
IGateUtility _gateUtility;
5. public ServiceStation(IGateUtility gateUtility)
{
this._gateUtility = gateUtility;
}
public void OpenForService()
10. {
_gateUtility.OpenGate();
}
public void DoService()
15. {
//Check if service station is opened and then
//complete the vehicle service
}
20. public void CloseForDay()
{
_gateUtility.CloseGate();
}
}
25.
public class ServiceStationUtility : IGateUtility
{
public void OpenGate()
{
30. //Open the shop if the time is later than 9 AM
}
public void CloseGate()
{
35. //Close the shop if the time has crossed 6PM
}
}
40.public interface IGateUtility
{
void OpenGate();
void CloseGate();
}
//The following violates OCP because the addition of Mercedes requires changes in the mileage calculations
public class MileageCalculator
{
IEnumerable<Car> _cars;
public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }
5.
public void CalculateMileage()
{
foreach (var car in _cars)
{
10. if (car.Name == "Audi")
Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "10M");
else if (car.Name == "Mercedes")
Console.WriteLine("Mileage of the car {0} is {1}", car.Name, "20M");
}
15. }
}
//This fixes it
public class MileageCalculator
{
IEnumerable<Car> _cars;
public MileageCalculator(IEnumerable<Car> cars) { this._cars = cars; }
5.
public void CalculateMileage()
{
CarController controller = new CarController();
foreach (var car in _cars)
10. {
Console.WriteLine("Mileage of the car {0} is {1}", car.Name, controller.GetCarMileage(car.Name));
}
}
}
15.
public class CarController
{
List<ICar> cars;
public CarController()
20. {
cars = new List<ICar>();
cars.Add(new Audi());
cars.Add(new Mercedes());
}
25.
public string GetCarMileage(string name)
{
return cars.First(car => car.Name == name).GetMileage();
}
30.}
public interface ICar
{
string Name { get; set; }
35. string GetMileage();
}
public class Audi : ICar
{
40. public string Name { get; set; }
public string GetMileage()
{
return "10M";
45. }
}
public class Mercedes : ICar
{
50. public string Name { get; set; }
public string GetMileage()
{
return "20M";
55. }
}
//Violation because an object is forced to include methods not required
public interface IOrder
{
void Purchase();
void ProcessCreditCard();
5. }
public class OnlineOrder : IOrder
{
public void Purchase()
10. {
//Do purchase
}
public void ProcessCreditCard()
15. {
//process through credit card
}
}
20. public class InpersionOrder : IOrder
{
public void Purchase()
{
//Do purchase
25. }
public void ProcessCreditCard()
{
//Not required for inperson purchase
30. throw new NotImplementedException();
}
}
//Fixed by
public interface IOrder
{
void Purchase();
}
5.
public interface IOnlineOrder
{
void ProcessCreditCard();
}
10.
public class OnlineOrder : IOrder, IOnlineOrder
{
public void Purchase()
{
15. //Do purchase
}
public void ProcessCreditCard()
{
20. //process through credit card
}
}
public class InpersionOrder : IOrder
25. {
public void Purchase()
{
//Do purchase
}
30. }
Dependency Injection Principle states that there should be more abstraction between the higher level module and the lower level module. It is required to have loose coupling so that any change in the low level modules will not affect or there will be minimal impact at the higher level module. The ideal scenario would be when you write components for other applications to consume.
** Higher level module
** (vehicle controller)
|
| Connect should be abstract (DIP)
|
** Lower level module
** (car)
In the example provided in Fig 1.0 VehicleController is the higher level module, which consumes and uses the lower level module Car. If you want to change the vehicle to Truck instead of Car then VehicleController does not require any change if there is higher level of abstraction.
Inversion of Control is a technique to implement the Dependency Inversion Principle in C#. Inversion of control can be achieved by using interfaces or abstract class. The rule is that the lower level modules should sign up the contract to a single interface and the higher level module will consume only modules that are implementing the interface. This technique removes the dependency between the modules.
As shown in Fig 2.0 Car and Truck will implement the interface IVehicle and VehicleController will couple with the vehicles through the IVehicle interface thus increasing the level of abstraction between the layers.
VehicleController --> IVehicle
^ ^
| |
Car Truck
//An example of DIP in effect
public class Car : IVehicle
{
#region IVehicle Members
5.
public void Accelerate()
{
Console.WriteLine("Car accelerates...");
}
10.
public void Brake()
{
Console.WriteLine("Car stopped.");
}
15.
#endregion
}
public class Truck : IVehicle
20.{
#region IVehicle Members
public void Accelerate()
{
25. Console.WriteLine("Truck accelerates...");
}
public void Brake()
{
30. Console.WriteLine("Truck stopped.");
}
#endregion
}
35.
public interface IVehicle
{
void Accelerate();
void Brake();
40.}
public class VehicleController
{
IVehicle m_Vehicle;
45.
public VehicleController(IVehicle vehicle)
{
this.m_Vehicle = vehicle;
}
50.
public void Accelerate()
{
m_Vehicle.Accelerate();
}
55.
public void Brake()
{
m_Vehicle.Brake();
}
60.}
class Program
{
static void Main(string[] args)
65. {
IVehicle vehicle = new Car();
//IVehicle vehicle = new Truck();
VehicleController vehicleController = new VehicleController(vehicle);
70. vehicle.Accelerate();
vehicle.Brake();
Console.Read();
}
75.}
another example using an engine, startes etc.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SOLIDDesignPrinciples
{
#region Base Classes
public class Component : IComponent
{
public string Brand
{
get;
set;
}
public string Model
{
get;
set;
}
}
public abstract class Battery : Component, IBattery
{
public bool IsCharged
{
get;
set;
}
public abstract void EvaluateCharge();
}
#endregion Base Classes
#region Interfaces
public interface IComponent
{
string Brand { get; set; }
string Model { get; set; }
}
public interface IStarter : IComponent
{
IgnitionResult Start();
}
public interface IElectricStarter<T> : IStarter
{
T Battery { get; set; }
}
public interface IPneumaticStarter : IStarter
{
AirCompressor Compressor { get; set; }
}
public interface IHydraulicStarter : IStarter
{
HydraulicPump Pump { get; set; }
}
public interface IBattery : IComponent
{
bool IsCharged { get; set; }
void EvaluateCharge();
}
public interface IEngine : IComponent
{
IgnitionResult Start(IStarter starter);
}
#endregion Interfaces
#region Engine
public class Engine : Component, IEngine
{
public IgnitionResult Start(IStarter starter)
{
return starter.Start();
}
}
#endregion Engine
#region Starter Types
public class ElectricStarter<T> : Component, IElectricStarter<T>
where T : IBattery
{
public ElectricStarter(T battery)
{
this.Battery = battery;
}
public T Battery
{
get;
set;
}
public IgnitionResult Start()
{
this.Battery.EvaluateCharge();
if (this.Battery.IsCharged == true)
{
return IgnitionResult.Success;
}
else
{
return IgnitionResult.Failure;
}
}
}
public class PneumaticStarter : Component, IPneumaticStarter
{
public AirCompressor Compressor
{
get;
set;
}
public IgnitionResult Start()
{
//code here to initiate the pneumatic starter
return IgnitionResult.Success;
}
}
public class HydraulicStarter : Component, IHydraulicStarter
{
public HydraulicPump Pump
{
get;
set;
}
public IgnitionResult Start()
{
//code here to initiate the hydraulic starter
return IgnitionResult.Success;
}
}
#endregion Starter Types
#region Starter Support Components
public class WetFloodedBattery : Battery, IBattery
{
public override void EvaluateCharge()
{
//logic here to evaluate the charge
this.IsCharged = true;
}
}
public class CalciumCalciumBattery : Battery, IBattery
{
public override void EvaluateCharge()
{
//logic here to evaluate the charge
this.IsCharged = true;
}
}
public class VRLABattery : Battery, IBattery
{
public override void EvaluateCharge()
{
//logic here to evaluate the charge
this.IsCharged = true;
}
}
public class DeepCycleBattery : Battery, IBattery
{
public override void EvaluateCharge()
{
//logic here to evaluate the charge
this.IsCharged = true;
}
}
public class LithiumIonBattery : Battery, IBattery
{
public override void EvaluateCharge()
{
//logic here to evaluate the charge
this.IsCharged = true;
}
}
public class AirCompressor : Component
{
}
public class HydraulicPump : Component
{
}
#endregion Starter Support Components
#region Enums
public enum IgnitionResult
{
Success,
Failure
}
#endregion Enums
}
https://johnlnelson.com/2014/07/23/dependency-inversion-principle-in-c-solid-design-principles/
To implement Dependency Inversion, the higher-level modules must have control over the interfaces that they use to define their requirements for lower-level objects.
Now we’re getting somewhere! The Engine class owns the IStarter interface and thus controls what the classes that implement IStarter must provide. Nice! With this scenario, a change to a class that implements the IStarter interface that changes the interface is not valid.
So where is the inversion? If we go back to the first Engine/Starter diagram above, we see that changes within that scenario bubble from the bottom up. Changes to lower-level components force changes to higher-level components. With our new design, since the higher-level component owns and controls the interface, any change in the lower-level component that breaks its interface implementation is not valid unless the higher-level object allows it and the IStarter interface is changed as well. If the Engine must be changed in a way that affects the IStarter interface, the forcing of change is top down. In other words, the Starter must be changed as a result of a mandated change by the Engine. Make sense?