Introduction
SRP can be defined as “a class handles only one responsibility”. This is a very short definition for something influential on to the other principles of S.O.L.I.D. I believe that if we get this right, it will have a positive knock-on effect on the upcoming principles, so let’s get started! Practical Example Let’s say we have an online store where people can order some items or products and in the code base we have a class OrderProcessor that processes the new orders when people click on Pay Now button.
The reason OrderProcessor
is written is to carry out the following tasks, in other words OrderProcessor
class has the following responsibilities:
Here is the definition of OrderProcessor
class
public class OrderProcessor
{
public bool ProcessOrder(Order orderToProcess)
{
// 1) Check the credit card has been accepted.
int creditCardId = orderToProcess.CreditCardId;
CreditCard creditCardDetails = new CreditCard();
using (SqlConnection connect = new SqlConnection())
{
using (SqlCommand command = new SqlCommand())
{
command.Connection = connect;
command.CommandText = "<schema>.<spName>";
command.CommandType = CommandType.StoredProcedure;
SqlParameter idParam = new SqlParameter();
idParam.Direction = ParameterDirection.Input;
idParam.Value = creditCardId;
idParam.ParameterName = "@Id";
idParam.DbType = DbType.Int32;
command.Parameters.Add(idParam);
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
creditCardDetails.CardId = int.Parse(reader["Id"].ToString());
creditCardDetails.CardLongNumber = reader["CardLongNumber"].ToString();
creditCardDetails.CvcNumber = int.Parse(reader["CvcNumber"].ToString());
creditCardDetails.ExpiryDate = DateTime.Parse(reader["ExpiryDate"].ToString());
creditCardDetails.NameOnTheCard = reader["NameOnTheCard"].ToString();
creditCardDetails.StartDate = DateTime.Parse(reader["StartDate"].ToString());
}
}
}
}
// charge the total amount using the credit card details..
decimal amountToCharge = orderToProcess.OrderTotal;
using (WebClient webClient = new WebClient())
{
string response = webClient.DownloadString($"https://CreditCardProcessor/api/ProcessesPayments?amount={amountToCharge}&CreditCard={creditCardDetails}");
// processes response: check if its been successful or failure then proceed further....
}
// check the item is in the stock
Dictionary<int, bool> productAvailability = new Dictionary<int, bool>();
foreach (int productId in orderToProcess.ProductIds)
{
using (SqlConnection connection = new SqlConnection())
{
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
command.CommandText = "<schema>.<spName>";
command.CommandType = CommandType.StoredProcedure;
SqlParameter idParam = new SqlParameter();
idParam.Direction = ParameterDirection.Input;
idParam.Value = productId;
idParam.ParameterName = "@Id";
idParam.DbType = DbType.Int32;
command.Parameters.Add(idParam);
object resultObject = command.ExecuteScalar();
bool prductAvailable = bool.Parse(resultObject.ToString());
if (prductAvailable)
productAvailability.Add(productId, true);
}
}
}
// request item for reservation
ReservationServiceClientProxy client = new ReservationServiceClientProxy();
foreach (KeyValuePair<int, bool> nextProduct in productAvailability)
{
ReservationRequest requst = new ReservationRequest() { ProductId = nextProduct.Key };
ReservationResponse response = client.ReserveProduct(requst);
}
// calculate estimated time of delivery...
DeliveryService ds = new DeliveryService();
int totalMinutes = 0;
foreach (KeyValuePair<int, bool> nextProduct in productAvailability)
{
totalMinutes += ds.EstimateDeliveryTimeInMinutes(nextProduct.Key);
}
// email customer
int customerId = orderToProcess.CustomerId;
string customerEmail = string.Empty;
using (SqlConnection connection = new SqlConnection())
{
using (SqlCommand command = new SqlCommand())
{
command.Connection = connection;
command.CommandText = "<schema>.<spName>";
command.CommandType = CommandType.StoredProcedure;
SqlParameter idParam = new SqlParameter();
idParam.Direction = ParameterDirection.Input;
idParam.Value = customerId;
idParam.ParameterName = "@customerId";
idParam.DbType = DbType.Int32;
command.Parameters.Add(idParam);
object resultObject = command.ExecuteScalar();
customerEmail = resultObject.ToString();
}
}
MailMessage message = new MailMessage(new MailAddress("[email protected]"), new MailAddress(customerEmail));
message.Body = $"You item has been dispatched and will be delivered in {totalMinutes / 1440} days";
message.Subject = "Your order update!";
SmtpClient smtpClient = new SmtpClient("HostName/IPAddress");
smtpClient.Send(message);
return true;
}
}
As we can see in the class definition that OrderProcessor
has taken more than one responsibility. Lets turn our attention to the version 2 of OrderProcesser
class that is written by keeping SRP in mind.
public class OrderProcessorV2
{
public bool ProcessOrder(Order orderToProcess)
{
// 1) Check the credit card has been accepted.
CreditCardDataAccess creditCardAccess = new CreditCardDataAccess();
CreditCard cardDetails = creditCardAccess.GetCreditCardDetails(orderToProcess.CreditCardId);
// 2) Check the money has been charged – finance
PaymentProcessor paymentProcessor = new PaymentProcessor();
paymentProcessor.DebitAmount(orderToProcess.OrderTotal, cardDetails);
// 3) Check the item is in stock – inventory
InventoryService inventory = new InventoryService();
Dictionary<int, bool> productAvailability = inventory.CheckStockAvailability(orderToProcess.ProductIds);
foreach (int nextProductId in orderToProcess.ProductIds)
{
inventory.CheckStockAvailability(nextProductId);
}
// 4) Request the item for reservation
ReservationService reservation = new ReservationService();
foreach (int nextProductId in orderToProcess.ProductIds)
{
reservation.ReserveProduct(nextProductId);
}
// 5) Get the estimated delivery time
// calculate estimated time of delivery...
DeliveryService ds = new DeliveryService();
int totalMinutes = 0;
foreach (KeyValuePair<int, bool> nextProduct in productAvailability)
{
totalMinutes += ds.EstimateDeliveryTimeInMinutes(nextProduct.Key);
}
// 6) Email the confirmation to the customer
CustomerDataAccess customerDataAccess = new CustomerDataAccess();
Customer cust = customerDataAccess.GetCustomerDetails(orderToProcess.CustomerId);
EmailService mailService = new EmailService();
mailService.NotifyCustomer(cust.Email);
// if everything step is successful then return true..
}
}
The amount of code lines in OrderProcessorV2
has radically changed so does the readability. The overall responsibility of OrderProcessorV2
can be understood within the amount of time that has taken to read this line. This results in more productivity.