以對象取代數(shù)組(Replace Array with Object)問題你有一個(gè)數(shù)組,其中的元素各自代表不同的東西永淌。String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";解決以對象替換數(shù)組崎场。對于數(shù)組中的每個(gè)元素,以一個(gè)字段來表示遂蛀。Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");數(shù)據(jù)泥團(tuán)數(shù)據(jù)泥團(tuán)(Data Clumps)有時(shí)谭跨,代碼的不同部分包含相同的變量組(例如用于連接到數(shù)據(jù)庫的參數(shù))。這些綁在一起出現(xiàn)的數(shù)據(jù)應(yīng)該擁有自己的對象。
基本類型偏執(zhí)?
基本類型偏執(zhí)(Primitive Obsession)
使用基本類型而不是小對象來實(shí)現(xiàn)簡單任務(wù)(例如貨幣螃宙、范圍蛮瞄、電話號碼字符串等)。
使用常量編碼信息(例如一個(gè)用于引用管理員權(quán)限的常量USER_ADMIN_ROLE = 1)谆扎。
使用字符串常量作為字段名在數(shù)組中使用挂捅。
問題原因
類似其他大部分壞味道,基本類型偏執(zhí)誕生于類初建的時(shí)候堂湖。一開始闲先,可能只是不多的字段,隨著表示的特性越來越多苗缩,基本數(shù)據(jù)類型字段也越來越多饵蒂。
基本類型常常被用于表示模型的類型。你有一組數(shù)字或字符串用來表示某個(gè)實(shí)體酱讶。
還有一個(gè)場景:在模擬場景退盯,大量的字符串常量被用于數(shù)組的索引。
解決方法
大多數(shù)編程語言都支持基本數(shù)據(jù)類型和結(jié)構(gòu)類型(類泻肯、結(jié)構(gòu)體等)渊迁。結(jié)構(gòu)類型允許程序員將基本數(shù)據(jù)類型組織起來,以代表某一事物的模型灶挟。
基本數(shù)據(jù)類型可以看成是機(jī)構(gòu)類型的積木塊琉朽。當(dāng)基本數(shù)據(jù)類型數(shù)量成規(guī)模后,將它們有組織地結(jié)合起來稚铣,可以更方便的管理這些數(shù)據(jù)箱叁。
如果你有大量的基本數(shù)據(jù)類型字段,就有可能將其中部分存在邏輯聯(lián)系的字段組織起來惕医,形成一個(gè)類耕漱。更進(jìn)一步的是,將與這些數(shù)據(jù)有關(guān)聯(lián)的方法也一并移入類中抬伺。為了實(shí)現(xiàn)這個(gè)目標(biāo)螟够,可以嘗試以類取代類型碼(Replace Type Code with Class)。
如果基本數(shù)據(jù)類型字段的值是用于方法的參數(shù)峡钓,可以使用引入?yún)?shù)對象(Introduce Parameter Object)或保持對象完整(Preserve Whole Object)妓笙。
如果想要替換的數(shù)據(jù)值是類型碼,而它并不影響行為能岩,則可以運(yùn)用以類取代類型碼(Replace Type Code with Class)將它替換掉寞宫。如果你有與類型碼相關(guān)的條件表達(dá)式,可運(yùn)用以子類取代類型碼(Replace Type Code with Subclass)或以狀態(tài)/策略模式取代類型碼(Replace Type Code with State/Strategy)加以處理捧灰。
如果你發(fā)現(xiàn)自己正從數(shù)組中挑選數(shù)據(jù)淆九,可運(yùn)用以對象取代數(shù)組(Replace Array with Object)统锤。
收益
多虧了使用對象替代基本數(shù)據(jù)類型毛俏,使得代碼變得更加靈活炭庙。
代碼變得更加易讀和更加有組織。特殊數(shù)據(jù)可以集中進(jìn)行操作煌寇,而不像之前那樣分散焕蹄。不用再猜測這些陌生的常量的意義以及它們?yōu)槭裁丛跀?shù)組中。
更容易發(fā)現(xiàn)重復(fù)代碼阀溶。
重構(gòu)方法說明
以類取代類型碼(Replace Type Code with Class)
問題
類之中有一個(gè)數(shù)值類型碼腻脏,但它并不影響類的行為。
解決
以一個(gè)新的類替換該數(shù)值類型碼银锻。
引入?yún)?shù)對象(Introduce Parameter Object)
問題
某些參數(shù)總是很自然地同時(shí)出現(xiàn)永品。
解決
以一個(gè)對象來取代這些參數(shù)。?
保持對象完整(Preserve Whole Object)
問題
你從某個(gè)對象中取出若干值击纬,將它們作為某一次函數(shù)調(diào)用時(shí)的參數(shù)鼎姐。
int low = daysTempRange.getLow(); int high = daysTempRange.getHigh(); boolean withinPlan = plan.withinRange(low, high);
解決
改為傳遞整個(gè)對象。
boolean withinPlan = plan.withinRange(daysTempRange);
以子類取代類型碼(Replace Type Code with Subclass)
問題
你有一個(gè)不可變的類型碼更振,它會影響類的行為炕桨。
解決
以子類取代這個(gè)類型碼。
以狀態(tài)/策略模式取代類型碼(Replace Type Code with State/Strategy)
問題
你有一個(gè)類型碼肯腕,它會影響類的行為献宫,但你無法通過繼承消除它。
解決
以狀態(tài)對象取代類型碼实撒。
以對象取代數(shù)組(Replace Array with Object)
問題
你有一個(gè)數(shù)組姊途,其中的元素各自代表不同的東西。
String[] row = new String[3];
row[0] = "Liverpool";
row[1] = "15";
解決
以對象替換數(shù)組知态。對于數(shù)組中的每個(gè)元素捷兰,以一個(gè)字段來表示。
Performance row = new Performance();
row.setName("Liverpool");
row.setWins("15");
數(shù)據(jù)泥團(tuán)
數(shù)據(jù)泥團(tuán)(Data Clumps)
有時(shí)肴甸,代碼的不同部分包含相同的變量組(例如用于連接到數(shù)據(jù)庫的參數(shù))寂殉。這些綁在一起出現(xiàn)的數(shù)據(jù)應(yīng)該擁有自己的對象。
問題原因
通常原在,數(shù)據(jù)泥團(tuán)的出現(xiàn)時(shí)因?yàn)樵愀獾木幊探Y(jié)構(gòu)或“復(fù)制-粘貼式編程”友扰。
有一個(gè)判斷是否是數(shù)據(jù)泥團(tuán)的好辦法:刪掉眾多數(shù)據(jù)中的一項(xiàng)。這么做庶柿,其他數(shù)據(jù)有沒有因而失去意義村怪?如果它們不再有意義,這就是個(gè)明確的信號:你應(yīng)該為它們產(chǎn)生一個(gè)新的對象浮庐。
解決方法
首先找出這些數(shù)據(jù)以字段形式出現(xiàn)的地方甚负,運(yùn)用提煉類(Extract Class)將它們提煉到一個(gè)獨(dú)立對象中柬焕。
如果數(shù)據(jù)泥團(tuán)在函數(shù)的參數(shù)列中出現(xiàn),運(yùn)用引入?yún)?shù)對象(Introduce Parameter Object)將它們組織成一個(gè)類梭域。
如果數(shù)據(jù)泥團(tuán)的部分?jǐn)?shù)據(jù)出現(xiàn)在其他函數(shù)中斑举,考慮運(yùn)用保持對象完整(Preserve Whole Object)將整個(gè)數(shù)據(jù)對象傳入到函數(shù)中。
檢視一下使用這些字段的代碼病涨,也許富玷,將它們移入一個(gè)數(shù)據(jù)類是個(gè)不錯(cuò)的主意。
收益
提高代碼易讀性和組織性既穆。對于特殊數(shù)據(jù)的操作赎懦,可以集中進(jìn)行處理,而不像以前那樣分散幻工。
減少代碼量励两。
何時(shí)忽略
有時(shí)為了對象中的部分?jǐn)?shù)據(jù)而將整個(gè)對象作為參數(shù)傳遞給函數(shù),可能會產(chǎn)生讓兩個(gè)類之間不收歡迎的依賴關(guān)系囊颅,這中情況下可以不傳遞整個(gè)對象当悔。
重構(gòu)方法說明
提煉類(Extract Class)
問題
某個(gè)類做了不止一件事。
解決
建立一個(gè)新類迁酸,將相關(guān)的字段和函數(shù)從舊類搬移到新類先鱼。
引入?yún)?shù)對象(Introduce Parameter Object)
問題
某些參數(shù)總是很自然地同時(shí)出現(xiàn)。
解決
以一個(gè)對象來取代這些參數(shù)奸鬓。
保持對象完整(Preserve Whole Object)
問題
你從某個(gè)對象中取出若干值焙畔,將它們作為某一次函數(shù)調(diào)用時(shí)的參數(shù)。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解決
改為傳遞整個(gè)對象串远。
boolean withinPlan = plan.withinRange(daysTempRange);
過大的類
過大的類(Large Class)
一個(gè)類含有過多字段宏多、函數(shù)、代碼行澡罚。
問題原因
類通常一開始很小伸但,但是隨著程序的增長而逐漸膨脹。
類似于過長函數(shù)留搔,程序員通常覺得在一個(gè)現(xiàn)存類中添加新特性比創(chuàng)建一個(gè)新的類要容易更胖。
解決方法
設(shè)計(jì)模式中有一條重要原則:職責(zé)單一原則。一個(gè)類應(yīng)該只賦予它一個(gè)職責(zé)隔显。如果它所承擔(dān)的職責(zé)太多却妨,就該考慮為它減減負(fù)。
如果過大類中的部分行為可以提煉到一個(gè)獨(dú)立的組件中括眠,可以使用提煉類(Extract Class)彪标。
如果過大類中的部分行為可以用不同方式實(shí)現(xiàn)或使用于特殊場景,可以使用提煉子類(Extract Subclass)掷豺。
如果有必要為客戶端提供一組操作和行為捞烟,可以使用提煉接口(Extract Interface)薄声。
如果你的過大類是個(gè) GUI 類,可能需要把數(shù)據(jù)和行為移到一個(gè)獨(dú)立的領(lǐng)域?qū)ο笕ヌ饣D憧赡苄枰獌蛇吀鞅A粢恍┲貜?fù)數(shù)據(jù)默辨,并保持兩邊同步。復(fù)制被監(jiān)視數(shù)據(jù)(Duplicate Observed Data)可以告訴你怎么做婴程。
收益
重構(gòu)過大的類可以使程序員不必記住一個(gè)類中大量的屬性廓奕。
在大多數(shù)情況下抱婉,分割過大的類可以避免代碼和功能的重復(fù)档叔。
重構(gòu)方法說明
提煉類(Extract Class)
問題
某個(gè)類做了不止一件事。
解決
建立一個(gè)新類蒸绩,將相關(guān)的字段和函數(shù)從舊類搬移到新類衙四。
提煉子類(Extract Subclass)
問題
一個(gè)類中有些特性僅用于特定場景。
解決
創(chuàng)建一個(gè)子類患亿,并將用于特殊場景的特性置入其中传蹈。
提煉接口(Extract Interface)
問題
多個(gè)客戶端使用一個(gè)類部分相同的函數(shù)。另一個(gè)場景是兩個(gè)類中的部分函數(shù)相同步藕。
解決
移動相同的部分函數(shù)到接口中惦界。
復(fù)制被監(jiān)視數(shù)據(jù)(Duplicate Observed Data)
問題
如果存儲在類中的數(shù)據(jù)是負(fù)責(zé) GUI 的。
解決
一個(gè)比較好的方法是將負(fù)責(zé) GUI 的數(shù)據(jù)放入一個(gè)獨(dú)立的類咙冗,以確保 GUI 數(shù)據(jù)與域類之間的連接和同步沾歪。
過長函數(shù)
過長函數(shù)(Long Method)
一個(gè)函數(shù)含有太多行代碼。一般來說雾消,任何函數(shù)超過 10 行時(shí)灾搏,你就可以考慮是不是過長了。 函數(shù)中的代碼行數(shù)原則上不要超過 100 行立润。
問題的原因
通常情況下狂窑,創(chuàng)建一個(gè)新函數(shù)的難度要大于添加功能到一個(gè)已存在的函數(shù)。大部分人都覺得:“我就添加這么兩行代碼桑腮,為此新建一個(gè)函數(shù)實(shí)在是小題大做了泉哈。”于是破讨,張三加兩行丛晦,李四加兩行,王五加兩行添忘。采呐。。函數(shù)日益龐大搁骑,最終爛的像一鍋漿糊斧吐,再也沒人能完全看懂了又固。于是大家就更不敢輕易動這個(gè)函數(shù)了,只能惡性循環(huán)的往其中添加代碼煤率。所以仰冠,如果你看到一個(gè)超過 200 行的函數(shù),通常都是多個(gè)程序員東拼西湊出來的蝶糯。
解決函數(shù)
一個(gè)很好的技巧是:尋找注釋洋只。添加注釋,一般有這么幾個(gè)原因:代碼邏輯較為晦澀或復(fù)雜昼捍;這段代碼功能相對獨(dú)立识虚;特殊處理规求。 如果代碼前方有一行注釋埠居,就是在提醒你:可以將這段代碼替換成一個(gè)函數(shù)控妻,而且可以在注釋的基礎(chǔ)上給這個(gè)函數(shù)命名蚜印。如果函數(shù)有一個(gè)描述恰當(dāng)?shù)拿钟∠危筒恍枰タ磧?nèi)部代碼究竟是如何實(shí)現(xiàn)的麻裳。就算只有一行代碼甥桂,如果它需要以注釋來說明蚊夫,那也值得將它提煉到獨(dú)立函數(shù)中银择。
為了給一個(gè)函數(shù)瘦身多糠,可以使用提煉函數(shù)(Extract Method)。
如果局部變量和參數(shù)干擾提煉函數(shù)浩考,可以使用以查詢?nèi)〈R時(shí)變量(Replace Temp with Query)夹孔,引入?yún)?shù)對象(Introduce Parameter Object)或保持對象完整(Preserve Whole Object)。
如果前面兩條沒有幫助怀挠,可以通過以函數(shù)對象取代函數(shù)(Replace Method with Method Object)嘗試移動整個(gè)函數(shù)到一個(gè)獨(dú)立的對象中析蝴。
條件表達(dá)式和循環(huán)常常也是提煉的信號。對于條件表達(dá)式绿淋,可以使用分解條件表達(dá)式(Decompose Conditional)闷畸。至于循環(huán),應(yīng)該使用提煉函數(shù)(Extract Method)將循環(huán)和其內(nèi)的代碼提煉到獨(dú)立函數(shù)中吞滞。
收益
在所有類型的面向?qū)ο蟠a中佑菩,函數(shù)比較短小精悍的類往往生命周期較長。一個(gè)函數(shù)越長裁赠,就越不容易理解和維護(hù)殿漠。
此外,過長函數(shù)中往往含有難以發(fā)現(xiàn)的重復(fù)代碼佩捞。
性能
是否像許多人說的那樣绞幌,增加函數(shù)的數(shù)量會影響性能?在幾乎絕大多數(shù)情況下一忱,這種影響是可以忽略不計(jì)莲蜘,所以不用擔(dān)心谭确。 此外,現(xiàn)在有了清晰和易讀的代碼票渠,在需要的時(shí)候逐哈,你將更容易找到真正有效的函數(shù)來重組代碼和提高性能。
重構(gòu)方法說明
提煉函數(shù)(Extract Method)
問題
你有一段代碼可以組織在一起问顷。
void printOwing() {
? printBanner();
? //print details
? System.out.println("name: " + name);
? System.out.println("amount: " + getOutstanding());
}
解決
移動這段代碼到一個(gè)新的函數(shù)中昂秃,使用函數(shù)的調(diào)用來替代老代碼。
void printOwing() {
? printBanner();
? printDetails(getOutstanding());
}
void printDetails(double outstanding) {
? System.out.println("name: " + name);
? System.out.println("amount: " + outstanding);
}
以查詢?nèi)〈R時(shí)變量(Replace Temp with Query)
問題
將表達(dá)式的結(jié)果放在局部變量中杜窄,然后在代碼中使用肠骆。
double calculateTotal() {
? double basePrice = quantity * itemPrice;
? if (basePrice > 1000) {
? ? return basePrice * 0.95;
? }
? else {
? ? return basePrice * 0.98;
? }
}
解決
將整個(gè)表達(dá)式移動到一個(gè)獨(dú)立的函數(shù)中并返回結(jié)果。使用查詢函數(shù)來替代使用變量羞芍。如果需要哗戈,可以在其他函數(shù)中合并新函數(shù)。
double calculateTotal() {
? double basePrice = quantity * itemPrice;
? if (basePrice > 1000) {
? ? return basePrice * 0.95;
? }
? else {
? ? return basePrice * 0.98;
? }
}
引入?yún)?shù)對象(Introduce Parameter Object)
問題
某些參數(shù)總是很自然地同時(shí)出現(xiàn)荷科。
解決
以一個(gè)對象來取代這些參數(shù)。
保持對象完整(Preserve Whole Object)
問題
你從某個(gè)對象中取出若干值纱注,將它們作為某一次函數(shù)調(diào)用時(shí)的參數(shù)畏浆。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解決
改為傳遞整個(gè)對象。
boolean withinPlan = plan.withinRange(daysTempRange);
以函數(shù)對象取代函數(shù)(Replace Method with Method Object)
問題
你有一個(gè)過長函數(shù)狞贱,它的局部變量交織在一起刻获,以致于你無法應(yīng)用提煉函數(shù)(Extract Method) 。
class Order {
? //...
? public double price() {
? ? double primaryBasePrice;
? ? double secondaryBasePrice;
? ? double tertiaryBasePrice;
? ? // long computation.
? ? //...
? }
}
解決
將函數(shù)移到一個(gè)獨(dú)立的類中瞎嬉,使得局部變量成了這個(gè)類的字段蝎毡。然后,你可以將函數(shù)分割成這個(gè)類中的多個(gè)函數(shù)氧枣。
class Order {
? //...
? public double price() {
? ? return new PriceCalculator(this).compute();
? }
}
class PriceCalculator {
? private double primaryBasePrice;
? private double secondaryBasePrice;
? private double tertiaryBasePrice;
? public PriceCalculator(Order order) {
? ? // copy relevant information from order object.
? ? //...
? }
? public double compute() {
? ? // long computation.
? ? //...
? }
}
分解條件表達(dá)式(Decompose Conditional)
問題
你有復(fù)雜的條件表達(dá)式沐兵。
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
? charge = quantity * winterRate + winterServiceCharge;
}
else {
? charge = quantity * summerRate;
}
解決
根據(jù)條件分支將整個(gè)條件表達(dá)式分解成幾個(gè)函數(shù)。
if (notSummer(date)) {
? charge = winterCharge(quantity);
}
else {
? charge = summerCharge(quantity);
}
過長參數(shù)列
過長參數(shù)列(Long Parameter List)
一個(gè)函數(shù)有超過 3便监、4 個(gè)入?yún)?/p>
問題原因
過長參數(shù)列可能是將多個(gè)算法并到一個(gè)函數(shù)中時(shí)發(fā)生的扎谎。函數(shù)中的入?yún)⒖梢杂脕砜刂谱罱K選用哪個(gè)算法去執(zhí)行。
過長參數(shù)列也可能是解耦類之間依賴關(guān)系時(shí)的副產(chǎn)品烧董。例如毁靶,用于創(chuàng)建函數(shù)中所需的特定對象的代碼已從函數(shù)移動到調(diào)用函數(shù)的代碼處,但創(chuàng)建的對象是作為參數(shù)傳遞到函數(shù)中逊移。因此预吆,原始類不再知道對象之間的關(guān)系,并且依賴性也已經(jīng)減少胳泉。但是如果創(chuàng)建的這些對象拐叉,每一個(gè)都將需要它自己的參數(shù)觅够,這意味著過長參數(shù)列。
太長的參數(shù)列難以理解巷嚣,太多參數(shù)會造成前后不一致喘先、不易使用,而且一旦需要更多數(shù)據(jù)廷粒,就不得不修改它窘拯。
解決方案
如果向已有的對象發(fā)出一條請求就可以取代一個(gè)參數(shù),那么你應(yīng)該使用以函數(shù)取代參數(shù)(Replace Parameter with Methods)坝茎。在這里涤姊,,“已有的對象”可能是函數(shù)所屬類里的一個(gè)字段嗤放,也可能是另一個(gè)參數(shù)思喊。
你還可以運(yùn)用保持對象完整(Preserve Whole Object)將來自同一對象的一堆數(shù)據(jù)收集起來,并以該對象替換它們次酌。
如果某些數(shù)據(jù)缺乏合理的對象歸屬恨课,可使用引入?yún)?shù)對象(Introduce Parameter Object)為它們制造出一個(gè)“參數(shù)對象”。
收益
更易讀岳服,更簡短的代碼剂公。
重構(gòu)可能會暴露出之前未注意到的重復(fù)代碼。
何時(shí)忽略
這里有一個(gè)重要的例外:有時(shí)候你明顯不想造成"被調(diào)用對象"與"較大對象"間的某種依賴關(guān)系吊宋。這時(shí)候?qū)?shù)據(jù)從對象中拆解出來單獨(dú)作為參數(shù)纲辽,也很合情理。但是請注意其所引發(fā)的代價(jià)璃搜。如果參數(shù)列太長或變化太頻繁拖吼,就需要重新考慮自己的依賴結(jié)構(gòu)了。
重構(gòu)方法說明
以函數(shù)取代參數(shù)(Replace Parameter with Methods)
問題
對象調(diào)用某個(gè)函數(shù)这吻,并將所得結(jié)果作為參數(shù)吊档,傳遞給另一個(gè)函數(shù)。而接受該參數(shù)的函數(shù)本身也能夠調(diào)用前一個(gè)函數(shù)橘原。
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
解決
讓參數(shù)接受者去除該項(xiàng)參數(shù)籍铁,并直接調(diào)用前一個(gè)函數(shù)。
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
保持對象完整(Preserve Whole Object)
問題
你從某個(gè)對象中取出若干值趾断,將它們作為某一次函數(shù)調(diào)用時(shí)的參數(shù)拒名。
int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);
解決
改為傳遞整個(gè)對象。
boolean withinPlan = plan.withinRange(daysTempRange);
引入?yún)?shù)對象(Introduce Parameter Object)
問題
某些參數(shù)總是很自然地同時(shí)出現(xiàn)芋酌。
解決
以一個(gè)對象來取代這些參數(shù)增显。