拿什么拯救你,我的代碼--c#編碼規(guī)范實(shí)戰(zhàn)篇

此文為譯文,原文地址請(qǐng)點(diǎn)擊。
本文通過重構(gòu)一個(gè)垃圾代碼,闡述了如何寫出優(yōu)秀的代碼。開發(fā)人員及代碼審核人員需按照此規(guī)范開發(fā)和審核代碼。此規(guī)范以C#為例,JAVA的童鞋一并參考,C++的童鞋自行腦補(bǔ)吧。


簡(jiǎn)介

這篇文章的目的是展示如何將一段垃圾代碼重構(gòu)成一個(gè)干凈的、可擴(kuò)展性和可維護(hù)的代碼。我將解釋如何通過最佳實(shí)踐和更好的設(shè)計(jì)模式來改寫它。

閱讀本文你需要有以下基礎(chǔ):

  • c# 基礎(chǔ)
  • 依賴注入,工廠模式,策略模式

此文中的例子源于實(shí)際項(xiàng)目,這里不會(huì)有什么使用裝飾模式構(gòu)建的披薩,也不會(huì)使用策略模式的計(jì)算器,這些例子是非常好的說明,但是它們很難被在實(shí)際項(xiàng)目中使用。

一段垃圾代碼

在我們真實(shí)的產(chǎn)品中有這么一個(gè)類:

public class Class1
{
  public decimal Calculate(decimal amount, int type, int years)
  {
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; 
if (type == 1)
{
  result = amount;
}
else if (type == 2)
{
  result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
}
else if (type == 3)
{
  result = (0.7m * amount) - disc * (0.7m * amount);
}
else if (type == 4)
{
  result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
}
return result;
  }
}

這是一段非常糟糕的代碼(二胡:如果你沒覺得這段代碼很糟糕,那你目前狀態(tài)可能很糟糕了),我們不太清楚這個(gè)類的目的是什么。它可能是一個(gè)網(wǎng)上商城的折扣管理類,負(fù)責(zé)為客戶計(jì)算折扣。

這個(gè)類完全具備了不可讀、不可維護(hù)、不可擴(kuò)展的特點(diǎn),它使用了很多壞的實(shí)踐和反模式的設(shè)計(jì)。

下面我們逐步分析這里到底有多少問題?

  • 命名問題 - 我們只能通過猜測(cè)這個(gè)類到底是為了計(jì)算什么。這實(shí)在是在浪費(fèi)時(shí)間。
    如果我們沒有相關(guān)文檔或者重構(gòu)這段代碼,那我們下一次恐怕需要花大量的時(shí)間才能知道這段代碼的具體含義。

  • 魔數(shù) - 在這個(gè)例子中,你能猜到變量type是指客戶賬戶的狀態(tài)嗎。通過if-else來選擇計(jì)算優(yōu)惠后的產(chǎn)品價(jià)格。
    現(xiàn)在,我們壓根不知道賬戶狀態(tài)1,2,3,4分別是什么意思。
    此外,你知道0.1,0.7,0.5都是什么意思嗎?
    讓我們想象一下,如果你要修改下面這行代碼:
    result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));

  • 隱藏的BUG - 因?yàn)榇a非常糟糕,我們可能會(huì)錯(cuò)過非常重要的事情。試想一下,如果我們的系統(tǒng)中新增了一類賬戶狀態(tài),而新的賬戶等級(jí)不滿足任何一個(gè)if-else條件。這時(shí),返回值會(huì)固定為0。

  • 不可讀 - 我們不得不承認(rèn),這是一段不可讀的代碼。不可讀=更多的理解時(shí)間+增加產(chǎn)生錯(cuò)誤的風(fēng)險(xiǎn)

  • DRY – 不要產(chǎn)生重復(fù)的代碼
    我們可能不能一眼就找出它們,但它們確實(shí)存在。
    例如:disc *(amount - (0.1m * amount));
    同樣的邏輯:
    disc *(amount - (0.5m * amount));
    這里只有一個(gè)數(shù)值不一樣。如果我們無法擺脫重復(fù)的代碼,我們會(huì)遇到很多問題。比如某段代碼在五個(gè)地方有重復(fù),當(dāng)我們需要修改這部分邏輯時(shí),你很可能只修改到了2至3處。

  • 單一職責(zé)原則
    這個(gè)類至少具有三個(gè)職責(zé):
    1 選擇計(jì)算算法
    2 根據(jù)賬戶狀態(tài)計(jì)算折扣
    3 根據(jù)賬戶網(wǎng)齡計(jì)算折扣
    它違反了單一職責(zé)原則。這會(huì)帶來什么風(fēng)險(xiǎn)?如果我們將要修改第三個(gè)功能的話,會(huì)影響到另外第二個(gè)功能。這就意味著,我們每次修改都會(huì)改動(dòng)我們本不想修改的代碼。因此,我們不得不對(duì)整個(gè)類進(jìn)行測(cè)試,這實(shí)在很浪費(fèi)時(shí)間。

重構(gòu)

通過以下9布,我會(huì)告訴你們?nèi)绾伪苊馍鲜鲲L(fēng)險(xiǎn)并實(shí)現(xiàn)一個(gè)干凈的、可維護(hù)的、可測(cè)試的代碼。

  1. 命名,命名,命名
    這是良好代碼的最重要方面之一。我們只需要改變方法,參數(shù)和變量的命名?,F(xiàn)在,我們可以確切的知道下面的類是負(fù)責(zé)什么的了。

    public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
        if (accountStatus == 1)
        {
            priceAfterDiscount = price;
        }
        else if (accountStatus == 2)
        {
            priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
        }
        else if (accountStatus == 3)
        {
            priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
        }
        else if (accountStatus == 4)
        {
            priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
        }
    
        return priceAfterDiscount;
    }
    

但是我們?nèi)稳徊恢蕾~戶狀態(tài)1,2,3到底是什么意思。

  1. 魔數(shù)
    在C#中避免魔數(shù)我們一般采用枚舉來替換它們。這里使用AccountStatus 枚舉來替換if-else中的魔數(shù)。
    public enum AccountStatus { NotRegistered = 1, SimpleCustomer = 2, ValuableCustomer = 3, MostValuableCustomer = 4 }
    現(xiàn)在我們來看看重構(gòu)后的類,我們可以很容易的說出哪一個(gè)賬戶狀態(tài)應(yīng)該用什么算法來計(jì)算折扣?;旌腺~戶狀態(tài)的風(fēng)險(xiǎn)迅速的下降了。

     public class DiscountManager
     {
         public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
         {
             decimal priceAfterDiscount = 0;
             decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
    
             if (accountStatus == AccountStatus.NotRegistered)
             {
                 priceAfterDiscount = price;
             }
             else if (accountStatus == AccountStatus.SimpleCustomer)
             {
                 priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
             }
             else if (accountStatus == AccountStatus.ValuableCustomer)
             {
                 priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
             }
             else if (accountStatus == AccountStatus.MostValuableCustomer)
             {
                 priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
             }
             return priceAfterDiscount;
         }
     }
    
  2. 更多的代碼可讀性
    在這一步中,我們使用switch-case 來替換 if-else if來增強(qiáng)代碼可讀性。
    同時(shí),我還將某些長(zhǎng)度很長(zhǎng)的語句才分成兩行。比如:**priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));**
    被修改為:
    **priceAfterDiscount = (price - (0.5m * price));priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);**
    以下是完整的修改:

     public class DiscountManager
     {
         public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
         {
             decimal priceAfterDiscount = 0;
             decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
             switch (accountStatus)
             {
                 case AccountStatus.NotRegistered:
                     priceAfterDiscount = price;
                     break;
                 case AccountStatus.SimpleCustomer:
                     priceAfterDiscount = (price - (0.1m * price));
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
                 case AccountStatus.ValuableCustomer:
                     priceAfterDiscount = (0.7m * price);
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
                 case AccountStatus.MostValuableCustomer:
                     priceAfterDiscount = (price - (0.5m * price));
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
             }
             return priceAfterDiscount;
         }
     }
    
  3. 消除隱藏的BUG
    正如我們之前提到的,我們的ApplyDiscount方法可能將為新的客戶狀態(tài)返回0。
    我們?cè)鯓硬拍芙鉀Q這個(gè)問題?答案就是拋出NotImplementedException。
    當(dāng)我們的方法獲取賬戶狀態(tài)作為輸入?yún)?shù),但是參數(shù)值可能包含我們未設(shè)計(jì)到的未知情況。這時(shí),我們不能什么也不做,拋出異常是這時(shí)候最好的做法。

        public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
        {
            decimal priceAfterDiscount = 0;
            decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
            switch (accountStatus)
            {
                case AccountStatus.NotRegistered:
                    priceAfterDiscount = price;
                    break;
                case AccountStatus.SimpleCustomer:
                    priceAfterDiscount = (price - (0.1m * price));
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                case AccountStatus.ValuableCustomer:
                    priceAfterDiscount = (0.7m * price);
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                case AccountStatus.MostValuableCustomer:
                    priceAfterDiscount = (price - (0.5m * price));
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                default:
                    throw new NotImplementedException();
            }
            return priceAfterDiscount;
        }
    
  4. 分析算法
    在這個(gè)例子中,我們通過兩個(gè)標(biāo)準(zhǔn)來計(jì)算客戶折扣:

  • 賬戶狀態(tài)

  • 賬戶網(wǎng)齡
    通過網(wǎng)齡計(jì)算的算法都類似這樣:
    (discountForLoyaltyInPercentage * priceAfterDiscount)
    但是對(duì)于賬戶狀態(tài)為ValuableCustomer的算法卻是:
    0.7m * price
    我們把它修改成和其他賬戶狀態(tài)一樣的算法:
    price - (0.3m * price)

          public class DiscountManager
         {
         public decimal ApplyDiscount(decimal price, AccountStatus     accountStatus, int timeOfHavingAccountInYears)
         {
         decimal priceAfterDiscount = 0;
         decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
         switch (accountStatus)
         {
             case AccountStatus.NotRegistered:
                 priceAfterDiscount = price;
                 break;
             case AccountStatus.SimpleCustomer:
                 priceAfterDiscount = (price - (0.1m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             case AccountStatus.ValuableCustomer:
                 priceAfterDiscount = (price - (0.3m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             case AccountStatus.MostValuableCustomer:
                 priceAfterDiscount = (price - (0.5m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             default:
                 throw new NotImplementedException();
         }
         return priceAfterDiscount;
         }
         }
    
  1. 消除魔數(shù)的另一種方法
    使用靜態(tài)常量來替換魔數(shù)。0.1m,0.2m,0.3m,我m,我們并不知道它們是什么意思。
    此外decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;中,數(shù)字5也非常神秘。
    我們必須讓它們更具有描述性,這時(shí)使用常量會(huì)是一個(gè)比較好的辦法。
    我們來定義一個(gè)靜態(tài)類:

     public static class Constants
     {
     public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5;
     public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m;
     public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m;
     public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;
     }
    

接著修改DiscountManager類:

    public class DiscountManager
    {
    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                priceAfterDiscount = price;
                break;
            case AccountStatus.SimpleCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            case AccountStatus.ValuableCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            case AccountStatus.MostValuableCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            default:
                throw new NotImplementedException();
        }
        return priceAfterDiscount;
    }
    }
  1. 消除重復(fù)的代碼
    為了消除重復(fù)的代碼,這里將部分算法提取出來。首先,我們建立兩個(gè)擴(kuò)展方法:

     public static class PriceExtensions
     {
     public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize)
     {
         return price - (discountSize * price);
     }
    
     public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears)
     {
         decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
         return price - (discountForLoyaltyInPercentage * price);
     }
     }
    

通過方法名稱,我們就可以知道它的職責(zé)是什么,現(xiàn)在修改我們的例子:

    public class DiscountManager
    {
    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                priceAfterDiscount = price;
                break;
            case AccountStatus.SimpleCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            case AccountStatus.ValuableCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            case AccountStatus.MostValuableCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            default:
                throw new NotImplementedException();
        }
        return priceAfterDiscount;
    }
    }
  1. 刪除沒用的代碼
    我們應(yīng)該寫出簡(jiǎn)短的代碼,因?yàn)楹?jiǎn)短的代碼=減少BUG發(fā)生的機(jī)率,并且也讓我們縮短理解業(yè)務(wù)邏輯的時(shí)間。
    我們發(fā)現(xiàn),這里三種狀態(tài)的客戶調(diào)用了相同的方法:
    .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
    這里可以合并代碼:

     public class DiscountManager
     {
     public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
     {
         decimal priceAfterDiscount = 0;
         switch (accountStatus)
         {
             case AccountStatus.NotRegistered:
                 priceAfterDiscount = price;
                 break;
             case AccountStatus.SimpleCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);
                 break;
             case AccountStatus.ValuableCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS);
                 break;
             case AccountStatus.MostValuableCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS);
                 break;
             default:
                 throw new NotImplementedException();
         }
         priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
         return priceAfterDiscount;
     }
     }
    

9.最后,得到干凈的代碼
最后,讓我們通過引入依賴注入和工廠方法模式來得到最終的版本吧。
先來看卡最終結(jié)果:

    public class DiscountManager
    {
    private readonly IAccountDiscountCalculatorFactory _factory;
    private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;

    public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator)
    {
        _factory = factory;
        _loyaltyDiscountCalculator = loyaltyDiscountCalculator;
    }

    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
        priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
        return priceAfterDiscount;
    }
    }

    public interface ILoyaltyDiscountCalculator
    {
    decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears);
    }

    public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears)
    {
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
        return price - (discountForLoyaltyInPercentage * price);
    }
    }

    public interface IAccountDiscountCalculatorFactory
    {
    IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);
    }

    public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory
    {
    public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)
    {
        IAccountDiscountCalculator calculator;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                calculator = new NotRegisteredDiscountCalculator();
                break;
            case AccountStatus.SimpleCustomer:
                calculator = new SimpleCustomerDiscountCalculator();
                break;
            case AccountStatus.ValuableCustomer:
                calculator = new ValuableCustomerDiscountCalculator();
                break;
            case AccountStatus.MostValuableCustomer:
                calculator = new MostValuableCustomerDiscountCalculator();
                break;
            default:
                throw new NotImplementedException();
        }

        return calculator;
        }
    }

    public interface IAccountDiscountCalculator
    {
    decimal ApplyDiscount(decimal price);
    }

    public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price;
    }
    }

    public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);
    }
    }

    public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price);
    }
    }

    public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price);
    }
    }

首先,我們擺脫了擴(kuò)展方法(靜態(tài)類),如果我們想對(duì)ApplyDiscount方法進(jìn)行單元測(cè)試是比較困難的,廢除我們對(duì)PriceExtensions擴(kuò)展類也進(jìn)行測(cè)試。
為了避免這個(gè)問題,我們創(chuàng)建了DefaultLoyaltyDiscountCalculator 類來替換ApplyDiscountForTimeOfHavingAccount這個(gè)擴(kuò)展方法,此類還實(shí)現(xiàn)了ILoyaltyDiscountCalculator接口?,F(xiàn)在,當(dāng)我們要測(cè)試DiscountManager類時(shí),我們只構(gòu)造函數(shù)注入ILoyaltyDiscountCalculator接口的實(shí)現(xiàn)即可。這里使用了依賴注入。
通過這樣做,我們將網(wǎng)齡折扣的算法遷移到類似DefaultLoyaltyDiscountCalculator 的不同類中,這樣當(dāng)我們修改某一個(gè)算法不會(huì)覆蓋到其他業(yè)務(wù)。
對(duì)于根據(jù)賬戶狀態(tài)來計(jì)算折扣的業(yè)務(wù),我們需要在
DiscountManager
中刪除兩個(gè)職責(zé):

  • 根據(jù)賬戶狀態(tài)選擇計(jì)算的算法

  • 實(shí)現(xiàn)計(jì)算算法
    這里我們通過DefaultAccountDiscountCalculatorFactory工廠類來解決這個(gè)問題,DefaultAccountDiscountCalculatorFactory工廠類實(shí)現(xiàn)了IAccountDiscountCalculatorFactory接口。
    我們的工廠將決定選擇哪一個(gè)折扣算法。接著,工廠類被通過構(gòu)造函數(shù)注入到DiscountManager類中。
    下面我只需要在DiscountManager 中使用工廠:
    priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
    以上,我們解決了第一個(gè)問題,下面我們需要實(shí)現(xiàn)計(jì)算算法。根據(jù)賬戶狀態(tài),提供不同的算法,這正好符合策略模式。我們需要構(gòu)建三個(gè)策略:
    NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator
    已經(jīng)抽象出來的接口IAccountDiscountCalculator
    好了,現(xiàn)在我們有可一段干凈可讀的代碼了,這段代碼中所有的類都只有一個(gè)職責(zé):

  • DiscountManager - 管理

  • DefaultLoyaltyDiscountCalculator - 網(wǎng)齡計(jì)算折扣

  • DefaultAccountDiscountCalculatorFactory - 根據(jù)賬戶狀態(tài)選擇折扣策略

  • NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator-計(jì)算折扣算法
    我們來比較一下修改前后的代碼:

      public class Class1
      {
      public decimal Calculate(decimal amount, int type, int years)
      {
          decimal result = 0;
          decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100;
          if (type == 1)
          {
              result = amount;
          }
          else if (type == 2)
          {
              result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
          }
          else if (type == 3)
          {
              result = (0.7m * amount) - disc * (0.7m * amount);
          }
          else if (type == 4)
          {
              result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
          }
          return result;
      }
      }
    

修改后:

    public class DiscountManager
    {
    private readonly IAccountDiscountCalculatorFactory _factory;
    private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;

    public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator)
    {
        _factory = factory;
        _loyaltyDiscountCalculator = loyaltyDiscountCalculator;
    }

    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
        priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
        return priceAfterDiscount;
    }
    }

總結(jié)

本文通過簡(jiǎn)單易懂的方法重構(gòu)了一段問題代碼,它顯示了如何在實(shí)際情況中使用最佳實(shí)踐和設(shè)計(jì)模式來幫助我們寫出干凈的代碼。
就我的工作經(jīng)驗(yàn)來說,本文中出現(xiàn)的不良做法是經(jīng)常發(fā)生的。編寫這種代碼的人總是認(rèn)為他們能夠保持這種規(guī)則,但不幸的是系統(tǒng)和業(yè)務(wù)往往都會(huì)越來越復(fù)雜,每次修改這類代碼時(shí)都會(huì)帶來巨大的風(fēng)險(xiǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,899評(píng)論 0 33
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,540評(píng)論 19 139
  • 如何實(shí)施重構(gòu) 稍微復(fù)雜的重構(gòu)過程,都是由一系列的基本重構(gòu)手法組成. 《重構(gòu)》一書中針對(duì)各種重構(gòu)場(chǎng)景,給出了大量的重...
    MagicBowen閱讀 4,440評(píng)論 0 3
  • 本文約定: 1. Nhibernate簡(jiǎn)寫為NHB; 2. 本文例子的開發(fā)平臺(tái)為win2000pro+sp4, s...
    壹米玖坤閱讀 596評(píng)論 0 0
  • 大家好,我是白亦初。江湖人稱“小白”。 上一次,我主要是和大家分享了我在寫作營(yíng)里學(xué)到的兩大收獲。一是我們要重新地審...
    阿超有話說閱讀 622評(píng)論 9 8

友情鏈接更多精彩內(nèi)容