C#:如何将坏的代码重新编译为好的代码
自己的前言說(shuō)明:
?本文原作者:Radoslaw Sadowski,原文鏈接為:C# BAD PRACTICES: Learn how to make a good code by bad example。
本系列還有其他文章,后續(xù)將慢慢翻譯。
?
引言:
我的名字叫Radoslaw Sadowski,我現(xiàn)在是一個(gè)微軟技術(shù)開(kāi)發(fā)人員。我從開(kāi)始工作時(shí)就一直接觸的微軟技術(shù).
在工作一年后,我看到的質(zhì)量很差的代碼的數(shù)量基本上都可以寫(xiě)成一整本書(shū)了。
這些經(jīng)歷讓我變成了一個(gè)想要清潔代碼的強(qiáng)迫癥患者。
寫(xiě)這篇文章的目的是為了通過(guò)展示質(zhì)量很差的類(lèi)的例子來(lái)說(shuō)明如何書(shū)寫(xiě)出干凈的、可延伸的和可維護(hù)的代碼。我會(huì)通過(guò)好的書(shū)寫(xiě)方式和設(shè)計(jì)模式來(lái)解釋壞的代碼帶來(lái)的問(wèn)題,以及替換他的好的解決方法。
第一部分是針對(duì)那些擁有C#基礎(chǔ)知識(shí)的開(kāi)發(fā)人員——我會(huì)展示一些常見(jiàn)的錯(cuò)誤,然后再展示一些讓代碼變得可讀性的方法與技巧。高級(jí)部分主要針對(duì)那些至少擁有設(shè)計(jì)模式概念的開(kāi)發(fā)人員——我將會(huì)展示完全干凈的、單元可測(cè)試的代碼。
為了能夠理解這篇文章你需要至少掌握以下兩個(gè)部分的基本知識(shí):
C#語(yǔ)言
依賴(lài)注入、工廠(chǎng)設(shè)計(jì)模式、策略設(shè)計(jì)模式
本文中所涉及的例子都將會(huì)是現(xiàn)實(shí)中實(shí)實(shí)在在的具體的特性,而不是用裝飾模式來(lái)做披薩或者用策略模式來(lái)做計(jì)算器這樣的示例。
(ps解釋:看過(guò)設(shè)計(jì)模式相關(guān)的書(shū)籍的人應(yīng)該會(huì)知道很多這方面的書(shū)籍都是用這種例子,只是為了幫助讀者理解設(shè)計(jì)模式)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ? ? ? ??
因?yàn)槲野l(fā)現(xiàn)這種類(lèi)型的產(chǎn)品不好用來(lái)解釋,相反這些理論性的例子卻是非常適合用來(lái)在本文中做解釋的。
我們常常會(huì)聽(tīng)到說(shuō)不要用這個(gè),要用那個(gè),但是卻不知道這種替換的理由。今天我將會(huì)努力解釋和證明那些好的書(shū)寫(xiě)習(xí)慣以及設(shè)計(jì)模式是真的是在拯救我們的開(kāi)發(fā)生活!
?提示:
?在本文中我不會(huì)花時(shí)間來(lái)講解C#的特性和涉及模式之類(lèi)(我也解釋不完),網(wǎng)上有很多關(guān)于這方面的好的理論的例子。我將集中講述如何在我們?nèi)粘9ぷ髦惺褂眠@些東西。
例子是一種比較容易的突出我們要說(shuō)明的問(wèn)題的方法,但是僅限于描述的問(wèn)題——因?yàn)槲野l(fā)現(xiàn)當(dāng)我在學(xué)習(xí)哪些包含著主要代碼的例子時(shí),我發(fā)現(xiàn)在理解文章的總體思想方面會(huì)有困難。
?我不是說(shuō)我文中說(shuō)的方法是惟一的解決方式,我只是能保證這些方法將會(huì)是讓你的代碼變得更高質(zhì)量的途徑。
我并不關(guān)心下面這些代碼的什么錯(cuò)誤處理,日志記錄等等。我要表述的只是用來(lái)解決日常編碼一些問(wèn)題的方法。
那就開(kāi)始吧….
那些糟糕透了的類(lèi)...
下面的例子是我們現(xiàn)實(shí)中的類(lèi):
上面這個(gè)例子真的是一種非常差的書(shū)寫(xiě)方式。你能知道這個(gè)類(lèi)是用來(lái)干嘛的么?這個(gè)東西是用來(lái)做一些奇怪的運(yùn)算的么?我們文章就從他開(kāi)始入手來(lái)講解吧…
現(xiàn)在我來(lái)告訴你,剛剛那個(gè)類(lèi)是用來(lái)當(dāng)顧客在網(wǎng)上買(mǎi)東西的時(shí)候?yàn)樗麄冇?jì)算對(duì)應(yīng)折扣的折扣計(jì)算和管理的類(lèi)。
-難以置信吧!
-可是這是真的!
這種寫(xiě)法真的是將難以閱讀、難以維護(hù)和難以擴(kuò)展這三種集合在一起了,而且擁有著太差的書(shū)寫(xiě)習(xí)慣和錯(cuò)誤的模式。
除此之外還有其他什么問(wèn)題么?
1.命名方式-從源代碼中我們可以連蒙帶猜估計(jì)出來(lái)這個(gè)計(jì)算方法和輸出結(jié)果是什么。而且我們想要從這個(gè)類(lèi)中提取計(jì)算算法將會(huì)是一件非常困難的事情。
這樣帶來(lái)的危害是:
最嚴(yán)重的問(wèn)題是:浪費(fèi)時(shí)間,
?
如果我們需要滿(mǎn)足客戶(hù)的商業(yè)咨詢(xún),要像他們展示算法細(xì)節(jié),或者我們需要修改這段代碼,這將花費(fèi)我們很長(zhǎng)的時(shí)間去理解我們的計(jì)算方法的邏輯。即使我們不記錄他或重構(gòu)代碼,下次我們/其他開(kāi)發(fā)人員再看這段代碼的時(shí)候,還是需要花費(fèi)同等的時(shí)間來(lái)研究這些代碼是干嘛的。而且在修改的同時(shí)還容易出錯(cuò),導(dǎo)致原來(lái)的計(jì)算全部出錯(cuò)。
?2.魔法數(shù)字
?
在這個(gè)例子中type是變量,你能猜到它代表著客戶(hù)賬戶(hù)的等級(jí)么?If-else if語(yǔ)句是用來(lái)實(shí)現(xiàn)如何選擇計(jì)算出產(chǎn)品價(jià)格折扣的方法。
現(xiàn)在我們不知道什么樣的賬戶(hù)是1,2,3或4。現(xiàn)在想象一下,當(dāng)你不得不為了那些有價(jià)值的VIP客戶(hù)改變他們的折扣計(jì)算方式的時(shí)候,你試著從那些代碼中找出修改的方法---這個(gè)過(guò)程可能會(huì)花費(fèi)你很長(zhǎng)的時(shí)間不說(shuō),還很有可能犯錯(cuò)以至于修改那些基礎(chǔ)的一般的客戶(hù)的賬戶(hù),畢竟像2或者3這些詞語(yǔ)毫無(wú)描述性的。但是在我們犯錯(cuò)以后,那些一般的客戶(hù)卻很高興,因?yàn)樗麄兊玫搅薞IP客戶(hù)的折扣。:)
3.沒(méi)有明顯的bug
因?yàn)槲覀兊拇a質(zhì)量很差,而且可讀性非常差,所以我們可能輕易就忽略掉很多非常重要的事情。想象一下,現(xiàn)在突然在系統(tǒng)中增加一種新的客戶(hù)類(lèi)型-金卡用戶(hù),而在我們的系統(tǒng)中任何一種新的賬戶(hù)類(lèi)型最后獲得的價(jià)格將是0元。為什么呢?因?yàn)樵谖覀兊?strong>if-else if語(yǔ)句中沒(méi)有任何狀態(tài)是滿(mǎn)足新的狀態(tài)的,所以只要是未處理過(guò)的賬戶(hù)類(lèi)型,最后返回值都將變成0。一旦我們的老板發(fā)現(xiàn)這件事,他將會(huì)大發(fā)雷霆-畢竟他已經(jīng)免費(fèi)賣(mài)給這樣用戶(hù)很多很多東西了!
4.沒(méi)有可讀性
我們必須承認(rèn)上面這段代碼的可讀性是真的糟糕。
她讓我們花費(fèi)了太多的時(shí)間去理解這段代碼,同時(shí)代碼隱藏錯(cuò)誤的幾率太大了,而這就是沒(méi)有可讀性的最重要的定義。
?5.魔法數(shù)字(再次)
你從代碼中能知道類(lèi)似0.1,0.7,0.5這些數(shù)字的意思么?好的,我承認(rèn)我不知道。只有我們自己編寫(xiě)這些代碼我們才知道這是什么意思,別人是無(wú)法理解的。
你試試想想如果讓你修改下面這句代碼,你會(huì)怎么樣:
result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
因?yàn)檫@個(gè)方法完全不可讀,所以你修改的過(guò)程中只能?chē)L試著把第一個(gè)0.5改成0.4而保持第二個(gè)0.5不懂。這可能會(huì)是一個(gè)bug,但是卻是最好的最合適的修改方式。因?yàn)檫@個(gè)0.5什么都沒(méi)有告訴我們。
同樣的事也存在將years變量轉(zhuǎn)換到disc變量的轉(zhuǎn)換過(guò)程中:
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;
這是用來(lái)計(jì)算折扣率的,會(huì)通過(guò)賬戶(hù)在我們系統(tǒng)的時(shí)間的百分比來(lái)獲取。好的,那么現(xiàn)在問(wèn)題來(lái)了,如果時(shí)間剛剛好就是5呢?
6.簡(jiǎn)潔-不要反復(fù)做無(wú)用功
雖然第一眼看的時(shí)候不容易看出來(lái),但是仔細(xì)研究一下就會(huì)發(fā)現(xiàn):我們的代碼里有很多重復(fù)的地方。例如:disc?* (amount - (0.1m * amount));
而與之有同樣效果的還有(只是變了一個(gè)參數(shù)而已):disc?* (amount - (0.5m * amount))
在這兩個(gè)算術(shù)中,唯一的區(qū)別就只是一個(gè)靜態(tài)參數(shù),而我們完全可以用一個(gè)可變的參數(shù)來(lái)替代。
如果我們不試著在寫(xiě)代碼的時(shí)候從一直ctri+c,ctrl+v中擺脫出來(lái),那我們將遇到的問(wèn)題就是我們只能修改代碼中的部分功能,因?yàn)槲覀儾恢烙卸嗌俚胤叫枰薷摹I厦娴倪壿嬍怯?jì)算出在我們系統(tǒng)中每個(gè)客戶(hù)對(duì)應(yīng)年限獲得的折扣,所以如果我們只是貿(mào)然修改兩到三處,很容易造成其他地方的前后不一致。
7.每個(gè)類(lèi)有著太多的復(fù)雜的責(zé)任區(qū)域
我們寫(xiě)的類(lèi)至少背負(fù)了三個(gè)責(zé)任:
選擇計(jì)算的運(yùn)算法則
為每個(gè)不同狀態(tài)的賬戶(hù)計(jì)算折扣率
根據(jù)每個(gè)客人的年限計(jì)算出對(duì)應(yīng)的折扣率
這個(gè)違背了單一責(zé)任原則。那么這會(huì)帶來(lái)什么危害呢?如果我們想要改變上訴3個(gè)特性中的兩個(gè),那就意味著可能會(huì)碰觸到一些其他的我們并不想修改的特性。所以在修改的時(shí)候我們不得不重新測(cè)試所有的類(lèi),那么這就造成了很重的時(shí)間的浪費(fèi)。
那就開(kāi)始重構(gòu)吧…
在接下來(lái)的9個(gè)步驟中我將向你展示我們?nèi)绾伪苊馍显V問(wèn)題來(lái)構(gòu)建一個(gè)干凈的易維護(hù),同時(shí)又方便單元測(cè)試的看起來(lái)一目了然的代碼。
?
I:命名,命名,命名
恕我直言,這是代碼中最重要的一步。我們只是修改方法/參數(shù)/變量這些的名字,而現(xiàn)在我們可以直觀的了解到下面這個(gè)類(lèi)代表什么意思。
雖然如此,我們還是不理解1,2,3,4代表著什么,那就繼續(xù)往下吧!
II:魔法數(shù)
在C#中避免出現(xiàn)不理解的魔法數(shù)的方法是通過(guò)枚舉來(lái)替代。我通過(guò)枚舉方法來(lái)替代在if-else if?語(yǔ)句中出現(xiàn)的代替賬戶(hù)狀態(tài)的魔法數(shù)。
現(xiàn)在在看我們重構(gòu)了的類(lèi),我們可以很容易的說(shuō)出那個(gè)計(jì)算法則是用來(lái)根據(jù)不用狀態(tài)來(lái)計(jì)算折扣率的。將賬戶(hù)狀態(tài)弄混的幾率就大幅度減少了。
III:更多的可讀性
在這一步中我們將通過(guò)將if-else if?語(yǔ)句改為switch-case?語(yǔ)句,來(lái)增加文章的可讀性。
同時(shí),我也將一個(gè)很長(zhǎng)的計(jì)算方法拆分為兩句話(huà)來(lái)寫(xiě)。現(xiàn)在我們將“ 通過(guò)賬戶(hù)狀態(tài)來(lái)計(jì)算折扣率”與“通過(guò)賬戶(hù)年限來(lái)計(jì)算折扣率”這兩者分開(kāi)來(lái)計(jì)算。
例如:priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
我們將它重構(gòu)為:priceAfterDiscount = (price - (0.5m * price));
priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
這就是修改后的代碼:
IV:沒(méi)有明顯的bug
我們終于找到我們隱藏的bug了!
因?yàn)槲覄倓偺岬降奈覀兊姆椒ㄖ袑?duì)于不適合的賬戶(hù)狀態(tài)會(huì)在造成對(duì)于所有商品最后都返回0。雖然很不幸,但卻是真的。
那我們?cè)撊绾涡迯?fù)這個(gè)問(wèn)題呢?那就只有通過(guò)沒(méi)有錯(cuò)誤提示了。
你是不是會(huì)想,這個(gè)會(huì)不會(huì)是開(kāi)發(fā)的例外,應(yīng)該不會(huì)被提交到錯(cuò)誤提示中去?不,他會(huì)的!
當(dāng)我們的方法通過(guò)獲取賬戶(hù)狀態(tài)作為參數(shù)的時(shí)候,我們并不想程序讓我們不可預(yù)知的方向發(fā)展,造成不可預(yù)計(jì)的失誤。
?這種情況是絕對(duì)不允許出現(xiàn)的,所以我們必須通過(guò)拋出異常來(lái)防止這種情況。
下面的代碼就是通過(guò)拋出異常后修改的以防止出現(xiàn)不滿(mǎn)足條件的情況-修改方式是將拋出異常防止?switch-case語(yǔ)句中的default?句中。
V:分析計(jì)算方法
在我們的例子中我們有兩個(gè)定義給客戶(hù)的折扣率的標(biāo)準(zhǔn):
賬戶(hù)狀態(tài);
賬戶(hù)在我們系統(tǒng)中存在的年限
對(duì)于年限的計(jì)算折扣率的方法,所有的計(jì)算方法都有點(diǎn)類(lèi)似:
(discountForLoyaltyInPercentage * priceAfterDiscount)
當(dāng)然,也還是存在例外的:0.7m * price
所以我們把這個(gè)改成這樣:price - (0.3m * price)
現(xiàn)在我們將整理所有通過(guò)賬戶(hù)狀態(tài)的計(jì)算方法改為同一種格式:price - ((static_discount_in_percentages/100) * price)
VI:通過(guò)其他方式再擺脫魔法數(shù)
接下來(lái)讓我們的目光放在通過(guò)賬戶(hù)狀態(tài)計(jì)算折扣率的計(jì)算方法中的靜態(tài)變量:(static_discount_in_percentages/100)
然后帶入下面數(shù)字距離試試:0.1m,0.3m,0.5m
這些數(shù)字其實(shí)也是一種類(lèi)型的魔法數(shù)-他們也沒(méi)有直接告訴我們他們代表著什么。
我們也有同樣的情況,比如將“有賬戶(hù)的時(shí)間”折價(jià)為“忠誠(chéng)折扣”。
decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
數(shù)字5讓我們的代碼變得神秘了起來(lái)。
我們必須做些什么讓這個(gè)變得更具表現(xiàn)性。
我會(huì)用另外一種方法來(lái)避免魔法數(shù)的表述的出現(xiàn)-也就是C#中的常量(關(guān)鍵詞是const),我強(qiáng)烈建議在我們的應(yīng)用程序中專(zhuān)門(mén)定義一個(gè)靜態(tài)類(lèi)來(lái)存儲(chǔ)這些常量。
在我們的例子中,我是創(chuàng)建了下面的類(lèi):
經(jīng)過(guò)一定的修改,我們的DiscountManager類(lèi)就變成了這樣了:
我希望你也認(rèn)同我這個(gè)方法會(huì)更加使代碼自身變得更具有說(shuō)明性:)
VII:不要再重復(fù)啦!
?
我們可以通過(guò)分拆算法的方式來(lái)移動(dòng)我們的計(jì)算方法,而不是僅僅簡(jiǎn)單的復(fù)制代碼。
我們會(huì)通過(guò)擴(kuò)展方法。
首先我們會(huì)創(chuàng)建兩個(gè)擴(kuò)展方法。
正如方法的名字一般,我不再需要單獨(dú)解釋一次他們的功能是什么。現(xiàn)在就開(kāi)始在我們的例子中使用這些代碼吧:
擴(kuò)展方法讓代碼看起來(lái)更加友善了,但是這個(gè)代碼還是靜態(tài)的類(lèi),所以會(huì)讓你單元測(cè)試的時(shí)候遇到困難,甚至不可能。那么出于擺脫這個(gè)問(wèn)題的打算我們?cè)谧詈笠徊絹?lái)解決這個(gè)問(wèn)題。我將展示這些是如何簡(jiǎn)化我們的工作生活的。但是對(duì)于我個(gè)人而言,我喜歡,但是并不算是熱衷粉。
不管怎樣,你現(xiàn)在同意我們的代碼看起來(lái)友善多了這一點(diǎn)么?
那我們就繼續(xù)下去吧!
VIII:移除那些多余的代碼
在寫(xiě)代碼的時(shí)候原則上是我們的代碼越是精簡(jiǎn)越好。精簡(jiǎn)的代碼的意味著,越少的錯(cuò)誤的可能性,在閱讀理解代碼邏輯的時(shí)候花費(fèi)的時(shí)間越少。
所以現(xiàn)在開(kāi)始精簡(jiǎn)我們的代碼吧。
我們可以輕易發(fā)現(xiàn)我們?nèi)N客戶(hù)賬戶(hù)下有著相同的方法:
.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
我們可不可以只寫(xiě)一次呢?我們之前將未注冊(cè)的用戶(hù)放在了拋出異常中,因?yàn)槲覀兊恼劭勐手粫?huì)計(jì)算注冊(cè)用戶(hù)的年限,并沒(méi)有給未注冊(cè)用戶(hù)留有功能設(shè)定。所以,我們應(yīng)該給未注冊(cè)用戶(hù)設(shè)定的時(shí)間為多少呢? -0年
那么對(duì)應(yīng)的折扣率也將變成0了,這樣我們就可以安全的將折扣率交付給未注冊(cè)用戶(hù)使用了,那就開(kāi)始吧!
我們還可以將這一行移除到switch-case語(yǔ)句外面。好處就是:更少的代碼量!
IX:提高-最后的得到干凈整潔的代碼
好了,現(xiàn)在我們可以像閱讀一本書(shū)一樣方便來(lái)審視我們的代碼了,但是這就夠了么?我們可以將代碼變得超級(jí)精簡(jiǎn)的!
好的,那就開(kāi)始做一些改變來(lái)實(shí)現(xiàn)這個(gè)目標(biāo)吧。我們可以使用依賴(lài)注入和使用策略模式這兩種方式。
這就是我們今天最后整理出來(lái)的代碼了:
首先我們擺脫了擴(kuò)展方法(也就是靜態(tài)類(lèi)),之所以要擺脫這種是因?yàn)閿U(kuò)展方法與折扣計(jì)算方法之間存在了緊耦合的關(guān)系。如果我們想要單元測(cè)試我們的方法ApplyDiscount的時(shí)候?qū)⒆兊貌惶菀?#xff0c;因?yàn)槲覀儽仨毥y(tǒng)一測(cè)試與之緊密關(guān)聯(lián)的類(lèi)PriceExtensions。
為了避免這個(gè),我創(chuàng)建了DefaultLoyaltyDiscountCalculator?類(lèi),這里面包含了ApplyDiscountForTimeOfHavingAccount擴(kuò)展方法,同事我通過(guò)抽象接口ILoyaltyDiscountCalculator隱藏了她的具體實(shí)現(xiàn)。現(xiàn)在,當(dāng)我想測(cè)試我們的類(lèi)DiscountManager的時(shí)候,我就可以通過(guò)?ILoyaltyDiscountCalculator模擬注入虛構(gòu)對(duì)象到DiscountManager類(lèi)中通過(guò)構(gòu)造函數(shù)顯示測(cè)試功能。這里我們運(yùn)用的就叫依賴(lài)注入模式。
在做這個(gè)的同時(shí),我們也將計(jì)算折扣率這個(gè)功能安全的移交到另一個(gè)不同的類(lèi)中,如果我們想要修改這一段的邏輯,那我們就只需要修改DefaultLoyaltyDiscountCalculator?類(lèi)就好了,而不需要改動(dòng)其他的地方,這樣減少了在改動(dòng)他的時(shí)候產(chǎn)生破壞其他地方的風(fēng)險(xiǎn),同時(shí)也不需要再增加單獨(dú)測(cè)試的時(shí)間了。
下面是我們?cè)?strong>DiscountManager類(lèi)中使用分開(kāi)的邏輯類(lèi):
priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
為了針對(duì)賬戶(hù)狀態(tài)的邏輯來(lái)計(jì)算折扣率,我創(chuàng)建了一些比較復(fù)雜的東西。我們?cè)?strong>DiscountManager類(lèi)中有兩個(gè)責(zé)任需要分解出去。
根據(jù)賬戶(hù)狀態(tài)如何選擇對(duì)應(yīng)的計(jì)算方法。
特殊計(jì)算方法的細(xì)節(jié)
為了將第一個(gè)責(zé)任移交出去,我創(chuàng)建了工廠(chǎng)類(lèi)(DefaultAccountDiscountCalculatorFactory),為了實(shí)現(xiàn)工廠(chǎng)模式,然后再把這個(gè)隱藏到抽象IAccountDiscountCalculatorFactory里面去。
我們的工廠(chǎng)會(huì)決定選擇哪種計(jì)算方法。最后我們通過(guò)依賴(lài)注冊(cè)模式構(gòu)造函數(shù)將工廠(chǎng)模式注射到DiscountManager類(lèi)中
下面就是運(yùn)用了工廠(chǎng)的DiscountManager類(lèi):
priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
?以上會(huì)針對(duì)不同的賬戶(hù)狀態(tài)返回何時(shí)的策略,然后調(diào)用ApplyDiscount?方法。
第一個(gè)責(zé)任已經(jīng)被交接出去了,接下來(lái)就是第二個(gè)了。
?接下來(lái)我們就開(kāi)始討論策略了…..
因?yàn)椴煌馁~戶(hù)狀態(tài)會(huì)有不用的折扣計(jì)算方法,所以我們需要不同的實(shí)現(xiàn)策略。座椅非常適用于策略模式。
在我們的例子中,我們有三種策略:
NotRegisteredDiscountCalculator
SimpleCustomerDiscountCalculator
MostValuableCustomerDiscountCalculator
他們包含了具體的折扣計(jì)算方法的實(shí)現(xiàn)并被藏在了抽象IAccountDiscountCalculator里。
這就允許我們的類(lèi)DiscountManager使用合適的策略,而不需要知道具體的實(shí)現(xiàn)。我們的類(lèi)只需要知道與ApplyDiscount方法相關(guān)的IAccountDiscountCalculator?接口返回的對(duì)象的類(lèi)型。
NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator, MostValuableCustomerDiscountCalculator這些類(lèi)包含了具體的通過(guò)賬戶(hù)狀態(tài)選擇適合計(jì)算的計(jì)算方法的實(shí)現(xiàn)。因?yàn)槲覀兊倪@三個(gè)策略看起來(lái)相似,我們唯一能做的基本上就只有針對(duì)這三種計(jì)算策略創(chuàng)建一個(gè)方法然后每個(gè)策略類(lèi)通過(guò)一個(gè)不用的參數(shù)來(lái)調(diào)用她。因?yàn)檫@會(huì)讓我們的代碼變得越來(lái)越多,所以我現(xiàn)在決定不這么做了。
好了,到目前為止我們的代碼變得可讀了,而且每個(gè)類(lèi)都只有一個(gè)責(zé)任了-這樣修改他的時(shí)候會(huì)單獨(dú)一一對(duì)應(yīng)了:
DiscountManager-管理代碼流
DefaultLoyaltyDiscountCalculator-可靠的計(jì)算折扣率的方法
DefaultAccountDiscountCalculatorFactory-決定根據(jù)賬戶(hù)狀態(tài)選擇哪個(gè)策略來(lái)計(jì)算。
NotRegisteredDiscountCalculator,?SimpleCustomerDiscountCalculator,?MostValuableCustomerDiscountCalculator?– 根據(jù)賬戶(hù)狀態(tài)計(jì)算折扣率
現(xiàn)在開(kāi)始比較現(xiàn)在與之前的方法:
這是我們的新的重構(gòu)的代碼:
總結(jié)
在本文中,代碼被極其簡(jiǎn)化了,使得所有的技術(shù)和模式的解釋更容易了。它展示了如何解決常見(jiàn)的編程問(wèn)題,以及使用良好的實(shí)踐和設(shè)計(jì)模式以適當(dāng)、干凈的方式解決這些問(wèn)題的好處。
在我的工作經(jīng)歷中,我多次在這篇文章中強(qiáng)調(diào)了不良的做法。它們顯然存在于許多應(yīng)用場(chǎng)合,而不是在一個(gè)類(lèi)中,如在我的例子中那樣,這使得發(fā)現(xiàn)它們更加困難,因?yàn)樗鼈冸[藏在適當(dāng)?shù)拇a之間。寫(xiě)這種代碼的人總是爭(zhēng)辯說(shuō),他們遵循的是簡(jiǎn)單愚蠢的規(guī)則。不幸的是,幾乎所有的系統(tǒng)都在成長(zhǎng),變得非常復(fù)雜。然后,這個(gè)簡(jiǎn)單的、不可擴(kuò)展的代碼中的每一個(gè)修改都是非常重要的,并且?guī)?lái)了巨大的風(fēng)險(xiǎn)。
請(qǐng)記住,您的代碼將長(zhǎng)期存在于生產(chǎn)環(huán)境中,并將在每個(gè)業(yè)務(wù)需求更改上進(jìn)行修改。因此編寫(xiě)過(guò)于簡(jiǎn)單、不可擴(kuò)展的代碼很快就會(huì)產(chǎn)生嚴(yán)重的后果。最后一點(diǎn)是對(duì)開(kāi)發(fā)人員有利,尤其是那些在你自己之后維護(hù)你的代碼。
如果你有一些問(wèn)題根據(jù)文章不要猶豫聯(lián)系我!
原文地址:https://www.cnblogs.com/Aries-rong/p/9289725.html
.NET社區(qū)新聞,深度好文,歡迎訪(fǎng)問(wèn)公眾號(hào)文章匯總 http://www.csharpkit.com
總結(jié)
以上是生活随笔為你收集整理的C#:如何将坏的代码重新编译为好的代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: GraphQL 的前世今生
- 下一篇: Asp.Net Core 快速邮件队列设