單一責任原則
介紹
SRP 可以定義為一個類只處理一個責任。對於對 SOLID 的其他原則有影響的事情,這是一個非常簡短的定義我相信如果我們做到這一點,它將對即將到來的原則產生積極的連鎖效應,所以讓我們開始吧! 實際示例假設我們有一個線上商店,人們可以在這裡訂購一些商品或產品。在程式碼庫中,我們有一個 OrderProcessor 類,當人們點選立即付款按鈕時,它會處理新訂單。
編寫 OrderProcessor
的原因是執行以下任務,換句話說,OrderProcessor
類具有以下職責:
- 檢查信用卡已被接受 - 財務
- 檢查已經收取的錢 - 財務
- 檢查物品是否有貨 - 庫存
- 請求預訂專案
- 獲取預計的交貨時間
- 將確認電子郵件傳送給客戶
這是 OrderProcessor
類的定義
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("Some.One@SuperCheapStore.co.uk"), 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;
}
}
正如我們在課堂定義中所看到的那樣,OrderProcessor
承擔了不止一項責任。讓我們把注意力轉向 OrderProcesser
類的第 2 版,它是通過記住 SRP 而編寫的。
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..
}
}
OrderProcessorV2
中的程式碼行數量發生了根本變化,可讀性也發生了變化。OrderProcessorV2
的總體責任可以在讀取此行所花費的時間內理解。這導致更高的生產力。