此文為譯文对省,原文地址請(qǐng)點(diǎn)擊。
本文通過(guò)重構(gòu)一個(gè)垃圾代碼晾捏,闡述了如何寫(xiě)出優(yōu)秀的代碼蒿涎。開(kāi)發(fā)人員及代碼審核人員需按照此規(guī)范開(kāi)發(fā)和審核代碼。此規(guī)范以C#為例惦辛,JAVA的童鞋一并參考劳秋,C++的童鞋自行腦補(bǔ)吧。
簡(jiǎn)介
這篇文章的目的是展示如何將一段垃圾代碼重構(gòu)成一個(gè)干凈的、可擴(kuò)展性和可維護(hù)的代碼玻淑。我將解釋如何通過(guò)最佳實(shí)踐和更好的設(shè)計(jì)模式來(lái)改寫(xiě)它嗽冒。
閱讀本文你需要有以下基礎(chǔ):
- c# 基礎(chǔ)
- 依賴注入,工廠模式补履,策略模式
此文中的例子源于實(shí)際項(xiàng)目添坊,這里不會(huì)有什么使用裝飾模式構(gòu)建的披薩,也不會(huì)使用策略模式的計(jì)算器箫锤,這些例子是非常好的說(shuō)明贬蛙,但是它們很難被在實(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;
}
}
這是一段非常糟糕的代碼(二胡:如果你沒(méi)覺(jué)得這段代碼很糟糕谚攒,那你目前狀態(tài)可能很糟糕了)阳准,我們不太清楚這個(gè)類的目的是什么。它可能是一個(gè)網(wǎng)上商城的折扣管理類馏臭,負(fù)責(zé)為客戶計(jì)算折扣溺职。
這個(gè)類完全具備了不可讀、不可維護(hù)位喂、不可擴(kuò)展的特點(diǎn),它使用了很多壞的實(shí)踐和反模式的設(shè)計(jì)乱灵。
下面我們逐步分析這里到底有多少問(wèn)題塑崖?
命名問(wèn)題 - 我們只能通過(guò)猜測(cè)這個(gè)類到底是為了計(jì)算什么。這實(shí)在是在浪費(fèi)時(shí)間痛倚。
如果我們沒(méi)有相關(guān)文檔或者重構(gòu)這段代碼规婆,那我們下一次恐怕需要花大量的時(shí)間才能知道這段代碼的具體含義。魔數(shù) - 在這個(gè)例子中蝉稳,你能猜到變量type是指客戶賬戶的狀態(tài)嗎抒蚜。通過(guò)if-else來(lái)選擇計(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ò)過(guò)非常重要的事情跃捣。試想一下喊括,如果我們的系統(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ù)值不一樣其爵。如果我們無(wú)法擺脫重復(fù)的代碼,我們會(huì)遇到很多問(wèn)題伸蚯。比如某段代碼在五個(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ì)帶來(lái)什么風(fēng)險(xiǎn)?如果我們將要修改第三個(gè)功能的話,會(huì)影響到另外第二個(gè)功能挥萌。這就意味著绰姻,我們每次修改都會(huì)改動(dòng)我們本不想修改的代碼。因此引瀑,我們不得不對(duì)整個(gè)類進(jìn)行測(cè)試狂芋,這實(shí)在很浪費(fèi)時(shí)間。
重構(gòu)
通過(guò)以下9布憨栽,我會(huì)告訴你們?nèi)绾伪苊馍鲜鲲L(fēng)險(xiǎn)并實(shí)現(xiàn)一個(gè)干凈的帜矾、可維護(hù)的、可測(cè)試的代碼屑柔。
-
命名屡萤,命名,命名
這是良好代碼的最重要方面之一掸宛。我們只需要改變方法死陆,參數(shù)和變量的命名。現(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到底是什么意思瞳遍。
-
魔數(shù)
在C#中避免魔數(shù)我們一般采用枚舉來(lái)替換它們。這里使用AccountStatus 枚舉來(lái)替換if-else中的魔數(shù)菌羽。
public enum AccountStatus { NotRegistered = 1, SimpleCustomer = 2, ValuableCustomer = 3, MostValuableCustomer = 4 }
現(xiàn)在我們來(lái)看看重構(gòu)后的類掠械,我們可以很容易的說(shuō)出哪一個(gè)賬戶狀態(tài)應(yīng)該用什么算法來(lái)計(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; } }
-
更多的代碼可讀性
在這一步中均唉,我們使用switch-case 來(lái)替換 if-else if來(lái)增強(qiáng)代碼可讀性。
同時(shí)肚菠,我還將某些長(zhǎng)度很長(zhǎng)的語(yǔ)句才分成兩行舔箭。比如:**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; } }
-
消除隱藏的BUG
正如我們之前提到的,我們的ApplyDiscount方法可能將為新的客戶狀態(tài)返回0蚊逢。
我們?cè)鯓硬拍芙鉀Q這個(gè)問(wèn)題层扶?答案就是拋出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; }
分析算法
在這個(gè)例子中,我們通過(guò)兩個(gè)標(biāo)準(zhǔn)來(lái)計(jì)算客戶折扣:
賬戶狀態(tài)
-
賬戶網(wǎng)齡
通過(guò)網(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; } }
-
消除魔數(shù)的另一種方法
使用靜態(tài)常量來(lái)替換魔數(shù)昼伴。0.1m,0.2m,0.3m,我m匾旭,我們并不知道它們是什么意思。
此外decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;中圃郊,數(shù)字5也非常神秘季率。
我們必須讓它們更具有描述性,這時(shí)使用常量會(huì)是一個(gè)比較好的辦法描沟。
我們來(lái)定義一個(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;
}
}
-
消除重復(fù)的代碼
為了消除重復(fù)的代碼,這里將部分算法提取出來(lái)鞭光。首先吏廉,我們建立兩個(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); } }
通過(guò)方法名稱,我們就可以知道它的職責(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;
}
}
-
刪除沒(méi)用的代碼
我們應(yīng)該寫(xiě)出簡(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.最后,得到干凈的代碼
最后晦毙,讓我們通過(guò)引入依賴注入和工廠方法模式來(lái)得到最終的版本吧生巡。
先來(lái)看卡最終結(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è)問(wèn)題,我們創(chuàng)建了DefaultLoyaltyDiscountCalculator 類來(lái)替換ApplyDiscountForTimeOfHavingAccount這個(gè)擴(kuò)展方法盐股,此類還實(shí)現(xiàn)了ILoyaltyDiscountCalculator接口∏恚現(xiàn)在,當(dāng)我們要測(cè)試DiscountManager類時(shí)疯汁,我們只構(gòu)造函數(shù)注入ILoyaltyDiscountCalculator接口的實(shí)現(xiàn)即可牲尺。這里使用了依賴注入。
通過(guò)這樣做幌蚊,我們將網(wǎng)齡折扣的算法遷移到類似DefaultLoyaltyDiscountCalculator 的不同類中谤碳,這樣當(dāng)我們修改某一個(gè)算法不會(huì)覆蓋到其他業(yè)務(wù)。
對(duì)于根據(jù)賬戶狀態(tài)來(lái)計(jì)算折扣的業(yè)務(wù)霹肝,我們需要在DiscountManager中刪除兩個(gè)職責(zé):
根據(jù)賬戶狀態(tài)選擇計(jì)算的算法
實(shí)現(xiàn)計(jì)算算法
這里我們通過(guò)DefaultAccountDiscountCalculatorFactory工廠類來(lái)解決這個(gè)問(wèn)題估蹄,DefaultAccountDiscountCalculatorFactory工廠類實(shí)現(xiàn)了IAccountDiscountCalculatorFactory接口。
我們的工廠將決定選擇哪一個(gè)折扣算法沫换。接著臭蚁,工廠類被通過(guò)構(gòu)造函數(shù)注入到DiscountManager類中。
下面我只需要在DiscountManager 中使用工廠:
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
以上讯赏,我們解決了第一個(gè)問(wèn)題垮兑,下面我們需要實(shí)現(xiàn)計(jì)算算法。根據(jù)賬戶狀態(tài)漱挎,提供不同的算法系枪,這正好符合策略模式。我們需要構(gòu)建三個(gè)策略:NotRegisteredDiscountCalculator磕谅,SimpleCustomerDiscountCalculator私爷,MostValuableCustomerDiscountCalculator已經(jīng)抽象出來(lái)的接口IAccountDiscountCalculator。
好了膊夹,現(xiàn)在我們有可一段干凈可讀的代碼了衬浑,這段代碼中所有的類都只有一個(gè)職責(zé):DiscountManager - 管理
DefaultLoyaltyDiscountCalculator - 網(wǎng)齡計(jì)算折扣
DefaultAccountDiscountCalculatorFactory - 根據(jù)賬戶狀態(tài)選擇折扣策略
-
NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator-計(jì)算折扣算法
我們來(lái)比較一下修改前后的代碼: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é)
本文通過(guò)簡(jiǎn)單易懂的方法重構(gòu)了一段問(wèn)題代碼,它顯示了如何在實(shí)際情況中使用最佳實(shí)踐和設(shè)計(jì)模式來(lái)幫助我們寫(xiě)出干凈的代碼放刨。
就我的工作經(jīng)驗(yàn)來(lái)說(shuō)工秩,本文中出現(xiàn)的不良做法是經(jīng)常發(fā)生的。編寫(xiě)這種代碼的人總是認(rèn)為他們能夠保持這種規(guī)則进统,但不幸的是系統(tǒng)和業(yè)務(wù)往往都會(huì)越來(lái)越復(fù)雜助币,每次修改這類代碼時(shí)都會(huì)帶來(lái)巨大的風(fēng)險(xiǎn)。