單一責任原則

介紹

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 的總體責任可以在讀取此行所花費的時間內理解。這導致更高的生產力。