在上一篇文章《代碼氣味–第一部分》中 ,我談到了膨脹器:它們是代碼氣味,可以識别為長方法,大型類,基元癡迷,長參數清單和資料塊。 在這一篇中,我想深入研究面向對象的濫用者和變更阻止者 。
面向對象的濫用者
當面向對象的原則不完整或應用不正确時,通常會發生這種類型的代碼異味。
切換語句
這種情況很容易識别:我們有一個開關箱。 但是,如果找到一系列ifs,您也應該考慮一下它的氣味。 (這是變相的開關盒)。 為什麼switch語句不好? 因為添加新條件後,您必須查找每次出現這種情況的情況。 是以,在與David交談時,他問我:如果将開關封裝到一個方法中會發生什麼情況呢? 這确實是一個好問題……如果您的開關盒僅用于“照顧”一種行為,僅此而已,那就可以了。 記住,識别出代碼的氣味并不意味着您必須經常使用它:這是一個權衡。 如果您發現您的switch語句已複制,并且每個複制都有不同的行為,那麼您不能簡單地将switch語句隔離在一個方法中。 您需要找到一個合适的“家”才能進入。根據經驗,當您處于這種情況時,應該考慮多态性。 我們可以在此處應用兩種重構技術:
- 用子類替換類型代碼此技術包括為每種開關情況建立子類并将相應的行為應用于這些子類。
- 用政策替換類型代碼與上述類似,在這種情況下,您應該使用以下模式之一: 狀态或政策 。
那麼什麼時候使用其中一個? 如果類型代碼沒有改變類的行為,則可以使用子類技術。 将每個行為分為适當的子類将強制執行“ 單一職責原則”,并通常使代碼更具可讀性。 如果需要添加其他情況,則隻需在代碼中添加一個新類,而無需修改任何其他代碼。 是以,您可以應用“ 打開/關閉原則” 。 當類型代碼影響類的行為時,應使用政策方法。 如果要更改類,字段和許多其他操作的狀态,則應使用State Pattern 。 如果它僅影響您選擇班級行為的方式,則“ 政策模式”是一個更好的選擇。
嗯...有點混亂,不是嗎? 是以,讓我們嘗試一個例子。
您有一個枚舉EmployeeType:
public enum EmployeeType
{
Worker,
Supervisor,
Manager
}
和一個班級的雇員:
public class Employee
{
private float salary;
private float bonusPercentage;
private EmployeeType employeeType;
public Employee(float salary, float bonusPercentage, EmployeeType employeeType)
{
this.salary = salary;
this.bonusPercentage = bonusPercentage;
this.employeeType = employeeType;
}
public float CalculateSalary()
{
switch (employeeType)
{
case EmployeeType.Worker:
return salary;
case EmployeeType.Supervisor:
return salary + (bonusPercentage * 0.5F);
case EmployeeType.Manager:
return salary + (bonusPercentage * 0.7F);
}
return 0.0F;
}
}
一切都很好。 但是,如果您需要計算年度獎金怎麼辦? 您将添加另一個這樣的方法:
public float CalculateYearBonus()
{
switch (employeeType)
{
case EmployeeType.Worker:
return 0;
case EmployeeType.Supervisor:
return salary + salary * 0.7F;
case EmployeeType.Manager:
return salary + salary * 1.0F;
}
return 0.0F;
}
看到重複的開關嗎? 是以,讓我們首先嘗試子類方法:這是超類:
abstract public class Employee
{
protected float salary;
protected float bonusPercentage;
public EmployeeFinal(float salary, float bonusPercentage)
{
this.salary = salary;
this.bonusPercentage = bonusPercentage;
}
abstract public float CalculateSalary();
virtual public float CalculateYearBonus()
{
return 0.0F;
}
}
這裡有子類:
public class Worker: Employee
{
two
public Worker(float salary, float bonusPercentage)
: base(salary, bonusPercentage)
{}
override public float CalculateSalary()
{
return salary;
}
}
public class Supervisor : Employee
{
public Supervisor(float salary, float bonusPercentage)
: base(salary, bonusPercentage)
{}
override public float CalculateSalary()
{
return salary + (bonusPercentage * 0.5F);
}
public override float CalculateYearBonus()
{
return salary + salary * 0.7F;
}
}
使用政策方法,我們将建立一個用于計算報酬的接口:
public interface IRetributionCalculator
{
float CalculateSalary(float salary);
float CalculateYearBonus(float salary);
}
有了适當的接口,我們現在可以将符合該協定的任何類别傳遞給員工,并計算正确的薪水/獎金。
public class Employee
{
private float salary;
private IRetributionCalculator retributionCalculator;
public Employee(float salary, IRetributionCalculator retributionCalculator)
{
this.salary = salary;
this.retributionCalculator = retributionCalculator;
}
public float CalculateSalary()
{
return retributionCalculator.CalculateSalary(salary);
}
public float CalculateYearBonus()
{
return retributionCalculator.CalculateYearBonus(salary);
}
}
臨時場
當我們正在計算一些需要多個輸入變量的大型算法時,就會發生這種情況。 通常,在類中建立這些字段沒有任何價值,因為它們僅用于特定的計算。 這也很危險,因為您必須確定在開始下一次計算之前重新初始化它們。 這裡最好的重構技術是将Replace Method與Method Object一起使用,這會将方法提取到單獨的類中。 然後,您可以将方法拆分為同一類中的多個方法。
拒絕遺贈
這段代碼的氣味很難檢測,因為當子類沒有使用其父類的所有行為時就會發生這種情況。 是以,好像子類“拒絕”了其父類的某些行為(“ bequest”)。
在這種情況下,如果繼續使用繼承沒有意義,那麼最好的重構技術就是更改為委派 :我們可以通過在子類中建立父類類型的字段來擺脫繼承。 這樣,每次您需要父類中的方法時,就将它們委托給這個新對象。
如果繼承是正确的事情,則從子類中移走所有不必要的字段和方法。 從子類和父類中提取所有方法和字段,并将它們放在新類中。 使這個新類成為超類,子類和父類應該從該超類繼承。 此技術稱為提取超類 。
具有不同接口的替代類
嗯,這種情況讓我想到了同一團隊成員之間的“缺乏溝通”,因為當我們有兩個類做相同的事情但其方法的名稱不同時,就會發生這種情況。 從重命名方法或移動方法開始 ,是以您可以使兩個類都實作相同的接口。 在某些情況下,兩個類中僅部分行為重複。 如果是這樣,請嘗試提取超類并使原始類成為子類。
變革預防者
好家夥! 這種代碼的味道是您真正要避免的。 這些就是在一個地方進行更改時,基本上也必須周遊整個代碼庫在其他地方進行更改的過程。 是以,這是我們所有人都想避免的噩夢!
發散變化
當您發現自己出于多種不同原因而更改同一班級時,就是這種情況。 這意味着您違反了“ 單一責任原則” (與關注點分離有關)。 此處應用的重構技術是“ 提取類”,因為您要将不同的行為提取到不同的類中。
彈槍手術
這意味着當您在一個班級中進行少量更改時,您必須同時更改幾個班級。 盡管它看起來與Divergent Change的氣味相同,但實際上它們是相反的: Divergent Change是對一個類進行多次更改的時間。 gun彈槍外科手術是指同時對多個類别進行一次更改時。
這裡要應用的重構技術是Move Method和/或Move Field 。 這将允許您将重複的方法或字段移到一個通用類。 如果該類不存在,請建立一個新類。 在原始類幾乎保持為空的情況下,也許您應該考慮一下該類是否是備援的,如果是,請使用Inline Class消除它:将其餘的方法/字段移至建立的新類之一。 這完全取決于原始類是否不再承擔任何責任。
并行繼承層次結構
在這種情況下,您發現自己為類B建立了一個新的子類,因為您向類A添加了一個子類。在這裡,您可以:首先,使一個層次結構引用另一個層次結構的執行個體。 在第一步之後,您可以使用“ 移動方法”和“ 移動字段”删除引用類中的層次結構。 您也可以在此處應用“ 訪客”模式 。
結論
對于面向對象的濫用者和變更阻止者 ,我認為,如果您知道如何将良好的設計應用于代碼,則可以更輕松地避免使用它們。 而這伴随着很多實踐。 今天,我讨論了一些重構技術,但還有很多。 您可以在Refactoring.com中找到有關所有内容的良好參考。 就像我在本系列的第一部分中所說的那樣,代碼的氣味不能總是被消除。 研究每種情況并做出決定:請記住,這始終是一個權衡。
翻譯自: https://www.javacodegeeks.com/2016/05/code-smells-part-ii.html