《重構:改善既有代碼的設計》-學習筆記一(+實戰解析)

来源:https://www.cnblogs.com/zenghw/archive/2018/05/23/9079541.html
-Advertisement-
Play Games

提前總結就是四招: 一、重覆的代碼提煉成函數 二、把過長的函數變小 三、參數列太長或變化太頻繁,參數對象化 四、大招:類的代碼行數太多,要考慮提煉子類。 ...


我不是個偉大的程式員;我只是個有著一些優秀習慣的好程式員而己

本人比較直接,不說虛的,直接上乾貨。

目錄

    Duplicated Code(重覆的代碼)

    Long Method(過長函數)

     Long Parameter List(過長參數列)

    Large Class(過大類)

提前總結就是四招:

    一、重覆的代碼提煉成函數

    二、把過長的函數變小

    三、參數列太長或變化太頻繁,參數對象化

    四、大招:類的代碼行數太多,要考慮提煉子類。

 

 一、重覆的代碼提煉成函數

    第一種情況是:同一個class內的兩個函數含有相同表達式(expression)。

void printOwing(String  _name) {
      Enumeration e =_orders.elements();
      double outstanding = 0.0;
      // print banner
      System.out.println ("**************************");
      System.out.println ("***** Customer Owes ******");
      System.out.println ("**************************");

      // calculate outstanding
      while (e.hasMoreElements()) {
          Order each = (Order) e.nextElement();
          outstanding += each.getAmount();
      }

     //print details
      System.out.println ("name:" + _name);
      System.out.println ("amount" + outstanding);

  }

 

實際上這三部分都可以提煉。

優化後的結果

void printOwing(String  _name) {

    printBanner();
    double outstanding = getOutstanding();
    printDetails(_name,outstanding);
}

void printBanner() {
      // print banner
      System.out.println ("**************************");
      System.out.println ("***** Customer Owes ******");
      System.out.println ("**************************");
  }
void printDetails (String _name,double outstanding) {

  System.out.println ("name:" + _name);
   System.out.println ("amount" + outstanding);
}

double getOutstanding() {

       Enumeration e = _orders.elements();
       double result = 0.0;
       while (e.hasMoreElements()) {
           Order each = (Order) e.nextElement();
          result = each.getAmount();
       }
       return result;

   }

第二種情況:兩個subclasses有相同的表達式,或者是相似的表達式

    優化的方法是:抽取相同的表達式(屬性和方法),放在父類里,兩個子類再去繼承。

**********************************************************************************           

如果是相似的表達式,好抽出共性的,則用模板函數設計模式來處理。

這裡用到了JAVA的兩個特性,繼承和多態。

優化的思路是:

1、在各個subclass 中分解目標函數,把有差異的部分變成入參,封裝成一個模板函數。

2、把模板函數放到父類中。

3、子類根據需要輸入不同的入參,得到需要的結果。

*********************************************************************************

如果是相似的表達式,差異的地方不好抽共性,則用模板函數設計模式來處理。

這裡用到了JAVA的兩個特性,繼承和覆寫(overrides)。

優化的思路是:

1、在各個subclass 中分解目標函數,使分解後的各個函數要不完全相同,要不完全不同。

2、父類有一個主函數包含完全相同的函數和完全不同的函數:相同的函數,抽到父類中,不相同的函數在父類中定義一個函數。

3、子類繼承父類,然後覆寫完全不同的函數,再調用主函數可得到期望的結果。

二、把過長的函數變小

百分之九十九的場合里,要把函數變小,只需使用Extract Method(第一招)。找到函數中適合集在一起的部分,將它們提煉出來形成一個新函數。

如果函數內有大量的參數和臨時變數,它們會對你的函數提煉形成阻礙。這時就要用Replace Temp with Query來消除這些臨時變數

Replace Temp with Query(以查詢取代臨時變數)

    優化思路:

1、找出只被賦值一次的臨時變數。

2、將該臨時變數聲明為final

3、編譯:這可確保該臨時變數的確只被賦值一次。

4、將臨時變數等號右側部分提煉到一個獨立函數中;

5、首先將函數聲明為private。日後你可能會發現有更多class需要使用 它,彼時你可再放鬆對它的保護。

6、編譯,測試:確保提煉出來的函數無任何連帶影響(副作用),結果不變;

7、把臨時變數全替換成獨立出來的函數;

以上,over!

例子:未優化代碼

double getPrice() {

       int basePrice = _quantity * _itemPrice;
       double discountFactor;
       if (basePrice > 1000) discountFactor = 0.95;
       else discountFactor = 0.98;
       return basePrice * discountFactor;

   }

開始優化 1~3步驟

double getPrice() {

      final int basePrice = _quantity * _itemPrice;
      final double discountFactor;
       if (basePrice > 1000) discountFactor = 0.95;
       else discountFactor = 0.98;
       return basePrice * discountFactor;

   }

4~6步驟

double getPrice() {

       final int basePrice = basePrice();
       final double discountFactor;
       if (basePrice > 1000) discountFactor = 0.95;
       else discountFactor = 0.98;
       return basePrice * discountFactor;

   }

   private int basePrice() {

   return _quantity * _itemPrice;

   }

7步驟

double getPrice() {

       final double discountFactor;
       if (basePrice() > 1000) discountFactor = 0.95;
       else discountFactor = 0.98;
       return basePrice() * discountFactor;

   }
private int basePrice() {

   return _quantity * _itemPrice;

   }

搞定basePrice之後,再以類似辦法提煉出一個discountFactor():

 double getPrice() {

       final double discountFactor = discountFactor();
       return basePrice() * discountFactor;

   }

 

   private double discountFactor() {

       if (basePrice() > 1000) return 0.95;
       else return 0.98;

   }

最後的效果

double getPrice() {

       return basePrice() * discountFactor();

   }
   private double discountFactor() {

       if (basePrice() > 1000) return 0.95;
       else return 0.98;

   }
   private int basePrice() {

   return _quantity * _itemPrice;

   }

通過以上的優化,一個大函數,已經變成了多個小函數,重點是代碼的可讀性提高了,順帶的代碼量變少。

三、參數對象化

當你看到一個函數的入參有四,五個,甚至更多時,且好幾個函數都使用這組入參,這時就要用參數對象化來優化代碼。這些函數可能隸屬同一個class,也可能隸屬不同的classes 。這樣一組參數就是所謂的Date Clump (數據泥團)」。這時用一個對象封裝這些參數,再用對象取代它們。

優化思路:

1、入參有四,五個,甚至更多時,就要著手優化;

2、用一個新的class封裝入參,並把這些參數設置為private嚴格保護起來,寫這些參數的get方法和set方法。

3、原函數的入參變成這個新的class對象,函數里的參數用class對象對應的屬性替換。

4、編譯測試;

5、將原先的參數全部去除之後,觀察有無適當函數可以運用Move Method 搬移到參數對象之中。

例子:未優化的代碼

@Autowired
    private AddressService addressService;
    public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){

        return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee);
    }

 

優化

@Autowired
    private AddressService addressService;
    public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){

        return addressService.inquireAddressList(pageNum,pageSize,output);
    }


    public class InquireAddressListInput(){
        private String addressName;
        private String mobile;
        private String zipCode;
        private String consignee;
        
    public String getConsignee() {
        return consignee;
    }

    public void setConsignee(String consignee) {
        this.consignee = consignee;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getZipCode() {
        return zipCode;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    public String getAddressName() {
        return addressName;
    }

    public void setAddressName(String addressName) {
        this.addressName = addressName;
    }
    }

四:大招-提煉類和提煉子類

如果想利用單一class做太多事情,其內往往就會出現太多instance變數。一旦如此,Duplicated Code也就接踵而至了。

Extract Class 是Extract Subclass 之外的另一種選擇,兩者之間的抉擇其實就是委托(delegation)和繼承(inheritance)之間的抉擇。

情況一:某個class做了應該由兩個classes做的事。(Extract Class

優化思路:

1、明確每個class所負的責任,該做什麼事情;

2、建立一個新class,用以表現從舊class中分離出來的責任;

3、建立「從舊class訪問新class」的連接關係;

4、每次搬移後,編譯、測試。

5、決定是否讓新的class曝光。

例子:未優化的代碼

 class Person{
   private String _name;
   private String _officeAreaCode;
   private String _officeNumber;
   
   public String getName() {
       return _name;
   }

   public String getTelephoneNumber() {
       return ("(" + _officeAreaCode + ") " + _officeNumber);
   }

   String getOfficeAreaCode() {
       return _officeAreaCode;
   }

   void setOfficeAreaCode(String arg) {
       _officeAreaCode = arg;
   }

   String getOfficeNumber() {
       return _officeNumber;
   }

   void setOfficeNumber(String arg) {
       _officeNumber = arg;
   }

  
}

優化1~2步驟

可以將「與電話號碼相關」的行為分離到一個獨立class中

class TelephoneNumber{

   private String _number;
   private String _areaCode;

   public String getTelephoneNumber() {
       return ("(" + _areaCode + ") " + _number);
   }

   String getAreaCode() {
       return _areaCode;
   }

   void setAreaCode(String arg) {
       _areaCode = arg;
   }

   String getNumber() {
       return _number;
   }

   void setNumber(String arg) {
       _number = arg;
   }

}

 

優化3步驟

class Person...

   private String _name;
   private TelephoneNumber _officeTelephone = new TelephoneNumber();

 public String getName() {
       return _name;
   }

   public String getTelephoneNumber(){
       return _officeTelephone.getTelephoneNumber();
   }

   TelephoneNumber getOfficeTelephone() {
       return _officeTelephone;
   }

情況二:class 中的某些特性(features)只被某些(而非全部)實體(instances)用到。Extract Subclass(提煉子類)

優化思路:

1、為source class 定義一個新的subclass

2、為這個新的subclass 提供構造函數。

    簡單的作法是:讓subclass 構造函數接受與superclass 構造函數相同的參數,並通過super 調用superclass 構造函數;

3、找出調用superclass 構造函數的所有地點。如果它們需要的是新建的subclass , 令它們改而調用新構造函數。

    如果subclass 構造函數需要的參數和superclass 構造函數的參數不同,可以使用Rename Method 修改其參數列。如果subclass 構造函數不需要superclass 構造函數的某些參數,可以使用Rename Method 將它們去除。

    如果不再需要直接實體化(具現化,instantiated)superclass ,就將它聲明為抽象類。

4、逐一使用Push Down MethodPush Down Field 將source class 的特性移到subclass 去。

5、每次下移之後,編譯並測試。

例子:未優化代碼

--用來決定當地修車廠的工作報價:

class JobItem ...

   public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
       _employee = employee;

   }

   public int getTotalPrice() {
   
       return getUnitPrice() * _quantity;
   }

   public int getUnitPrice(){
   
       return (_isLabor) ?
            _employee.getRate():
            _unitPrice;

   }

   public int getQuantity(){

       return _quantity;

   }

   public Employee getEmployee() {

       return _employee;

   }

   private int _unitPrice;
   private int _quantity;
   private Employee _employee;
   private boolean _isLabor;

 

 class Employee...
   public Employee (int rate) {

       _rate = rate;

   }

   public int getRate() {

       return _rate;

   }

   private int _rate;

 

優化1步驟

class LaborItem extends JobItem {}

優化2步驟

 class LaborItem extends JobItem {
    public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

       super (unitPrice, quantity, isLabor, employee);

   }

 }

這就足以讓新的subclass 通過編譯了。但是這個構造函數會造成混淆:某些參數是LaborItem 所需要的,另一些不是。稍後我再來解決這個問題。

優化3步驟

清理構造函數參數列

class JobItem...

  protected JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;
       _employee = employee;

   }

   public JobItem (int unitPrice, int quantity) {

       this (unitPrice, quantity, false, null)

   }

 

外部調用應該使用新構造函數:

       JobItem j2 = new JobItem (10, 15);

測試通過後,再使用Rename Method 修改subclass 構造函數:

class LaborItem

   public LaborItem (int quantity, Employee employee) {

       super (0, quantity, true, employee);

   }

可以將JobItem 的特性向下搬移。先從函數幵始,我先運用 Push Down Method 對付getEmployee() 函數:

 

 class LaborItem extends JobItem {
    public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

       super (unitPrice, quantity, isLabor, employee);

   }
   
    public LaborItem (int quantity, Employee employee) {

       super (0, quantity, true, employee);

   }
   
   public Employee getEmployee() {

       return _employee;

   }

 }
 
//因為_employee 值域也將在稍後被下移到LaborItem ,所以我現在先將它聲明為protected。
class JobItem...
  protected Employee _employee;

將_employee 值域聲明protected 之後,我可以再次清理構造函數,讓_employee 只在「即將去達的subclass 中」被初始化:

class JobItem...
   protected Employee _employee;
   protected JobItem (int unitPrice, int quantity, boolean isLabor) {

       _unitPrice = unitPrice;
       _quantity = quantity;
       _isLabor = isLabor;

   }

 class LaborItem ...

   public LaborItem (int quantity, Employee employee) {

       super (0, quantity, true);
        _employee = employee;

   }

下一個優化_isLabor 值域,_isLabor 在JobItem是值為false,在LaborItem值為true。

可以用多態常量函數。所謂「多態常量函數」會在不同的subclass 實現版本中返回不同的固定值

class JobItem...

   protected boolean isLabor() {

       return false;

   }

 class LaborItem...

   protected boolean isLabor() {

       return true;

   }

就可以擺脫_isLabor 值域了

通過多態代替條件的方式,重構代碼

class JobItem ...
 public int getUnitPrice(){
   
       return (isLabor()) ?
            _employee.getRate():
            _unitPrice;

   }

將它重構為:

class JobItem...

   public int getUnitPrice(){

       return _unitPrice;

   }

 class LaborItem...

   public int getUnitPrice(){

       return  _employee.getRate();

   }

使用某項值域的函數全被下移至subclass 後,我就可以使用 Push Down Field 將值域也下移。

最後的結果就是:

public class JobItem {

      protected  JobItem (int unitPrice, int quantity) {

           _unitPrice = unitPrice;
           _quantity = quantity;

       }

       public int getTotalPrice() {
       
           return getUnitPrice() * _quantity;
       }

       public int getUnitPrice(){
       
           return _unitPrice;

       }

       public int getQuantity(){

           return _quantity;

       }

       private int _unitPrice;
       private int _quantity;
}

//
public class LaborItem extends JobItem {

    private Employee _employee;

    public LaborItem(int quantity, Employee employee) {

        super(0, quantity);
        _employee = employee;
    }

    public Employee getEmployee() {

        return _employee;

    }

    public int getUnitPrice() {

        return _employee.getRate();

    }
}

//public class Employee {
    public Employee(int rate) {

        _rate = rate;

    }

    public int getRate() {

        return _rate;

    }

    private int _rate;
}

一個class如果擁有太多代碼,也適合使用Extract ClassExtract Subclass
想重構代碼,直接把以上四招看情況用上,更多精彩內容,請等待後續更新。

此文章也同步到了https://blog.csdn.net/shi_hong_fei_hei/article/details/80357078


作者:小虛竹
歡迎任何形式的轉載,但請務必註明出處。
限於本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Task 1: Open countries.xml, compose the following XQueries: 1. Return the area of Mongolia. 2. Return the names of all cities that have the same name ...
  • Python是一門解釋器語言,代碼想運行,必須通過解釋器執行,Python存在多種解釋器,分別基於不同語言開發,每個解釋器有不同的特點,但都能正常運行Python代碼,以下是常用的五種Python解釋器: CPython 當 從Python官方網站下載並安裝好Python2.7後,就直接獲得了一個官 ...
  • Github https://github.com/gongluck/Opencv3.4 study.git ...
  • 前面兩行引入相應的庫,真正的代碼就4行,夠簡單吧。第1行甚至可以不寫,它定義了圖的大小。第2行我們創建一個地圖,第3行把海岸線畫上,第4行顯示這個地圖,就是這樣: 你用 Java 的 4 行代碼畫一個地圖出來? 然後我們開始畫上國家,又是1行代碼: 就變成了這樣: 看上去有點變形,這是因為我們沒有添 ...
  • 通過列表生成式,我們可以直接創建一個列表。但是,受到記憶體限制,列表容量肯定是有限的。而且,如果創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那後面絕大多數元素占用的空間都白白浪費了。 如果列表元素可以按照某種演算法推算出來,可以在迴圈的過程中不斷推算出後續 ...
  • 天氣有點陰晴不定~ (截圖來自 金角大王) 1.學習了電腦概論(CPU/Memory/Disk,memory的存在是為瞭解決信息傳輸產生的時延) 2.瞭解了操作系統(OS) 3.初步認識python 3.1 註:解釋型語言最大的優點是可以跨平臺(只要windows、linux等不同的OS安裝瞭解釋 ...
  • 1 def run_time(func): 2 def new_fun(*args,**kwargs): 3 t0 = time.time() 4 print('star time: %s' % (time.strftime('%x', time.localtime()))) 5 back = fu... ...
  • 在互聯網開發中,我們在傳輸數據中經常需要對數據通過演算法進行加密,來保證信息的安全。 1.消息摘要演算法(Hash函數) 1.無論輸入的消息有多長,計算出來的消息摘要的長度總是固定的。 2.只能進行正向的信息摘要,而無法從摘要中恢復出任何的消息,即單向、不可逆的 3.好的摘要演算法,很難找到碰撞 常見的h ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...