单一责任原则

介绍

SRP 可以定义为一个类只处理一个责任。对于对 SOLID 的其他原则有影响的事情,这是一个非常简短的定义我相信如果我们做到这一点,它将对即将到来的原则产生积极的连锁效应,所以让我们开始吧! 实际示例假设我们有一个在线商店,人们可以在这里订购一些商品或产品。在代码库中,我们有一个 OrderProcessor 类,当人们点击立即付款按钮时,它会处理新订单。

编写 OrderProcessor 的原因是执行以下任务,换句话说,OrderProcessor 类具有以下职责:

  1. 检查信用卡已被接受 - 财务
  2. 检查已经收取的钱 - 财务
  3. 检查物品是否有货 - 库存
  4. 请求预订项目
  5. 获取预计的交货时间
  6. 将确认电子邮件发送给客户

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