单一责任原则
介绍
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
的总体责任可以在读取此行所花费的时间内理解。这导致更高的生产力。