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

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

Long Parameter List(過長參數列) Divergent Change(發散式變化) Shotgun Surgery(散彈式修改) Feature Envy(依戀情結) Data Clumps(數據泥團) Primitive Obsession(基本型別偏執) Switch Stat... ...


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

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

 目錄

  Long Parameter List(過長參數列)   Divergent Change(發散式變化)   Shotgun Surgery(散彈式修改)   Feature Envy(依戀情結)   Data Clumps(數據泥團)   Primitive Obsession(基本型別偏執)   Switch Statements(switch驚悚現身)  

Long Parameter List(過長參數列)

上一節有提過,當函數的入參過多時,可以用第三招,參數對象化,把參數封裝成對象,然後參數對象當成函數的入參,達到減少參數的作用。 除了參數對象化,還可以使用另一種方法來處理。 這種方法叫做:Replace Parameter with Method(以函數取代參數)

優化思路

前提,這個參數是只被賦值一次的 1、如果有必要,將參數的計算過程提煉到一個獨立函數中。 2、將函數內有使用參數的地方替換成獨立函數。 3、每次替換後,測試。 4、全部替換完成後,最後把這個參數刪除。 eg:未優化的代碼
 public double getPrice() {

      int basePrice = _quantity * _itemPrice;
      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice (basePrice, discountLevel);
      return finalPrice;

  }

  private double discountedPrice (int basePrice, int discountLevel) {
      if (discountLevel == 2) return basePrice * 0.1;
      else return basePrice * 0.05;
  }
優化 1,2,3步驟 優化參數basePrice
 public double getPrice() {

      int basePrice = getBasePrice();
      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice ( discountLevel);
      return finalPrice;

  }

  private double discountedPrice ( int discountLevel) {
      if (discountLevel == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private int getBasePrice(){
    return _quantity * _itemPrice;
  }
優化4步驟 去掉參數basePrice
  public double getPrice() {

      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice ( discountLevel);
      return finalPrice;

  }

  private double discountedPrice ( int discountLevel) {
      if (discountLevel == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private int getBasePrice(){
    return _quantity * _itemPrice;
  }
優化1,2,3,4步驟,去掉discountLevel參數,獨立函數返回值要為discountLevel 最後賦值的值
public double getPrice() {

      double finalPrice = discountedPrice ();
      return finalPrice;

  }

  private double discountedPrice () {
      if (getDiscountLevel() == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private double getDiscountLevel(){
      if (_quantity > 100) return 2;
      else return 1;
        
  }  
  
  private double getBasePrice(){
    return _quantity * _itemPrice;
  }
從上述代碼可看出,getPrice主函數finalPrice參數已經可以直接優化了。
public double getPrice() {

      return discountedPrice ();

  }
可以發現getPrice函數直接調用discountedPrice 函數,所以可用Inline Method(將函數內聯化) 合併這兩個函數
public double getPrice() {
    if (getDiscountLevel() == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;

  }
  
  private double getDiscountLevel(){
      if (_quantity > 100) return 2;
      else return 1;
        
  }  
  
  private double getBasePrice(){
    return _quantity * _itemPrice;
  }
我們只關心主函數的計算過程,一些過程性的計算,像上述這樣,獨立函數出來。代碼邏輯會十分清晰,可讀性很好。 存在一個重要的例外。如果明顯不希望封裝的對象與主對象之間存在某種依賴關係,可以把參數數據從封裝對象中抽出來,當成函數的參數。也是合理的。 但是要註意,當參數列太多或者參數變化頻繁時,就要考慮優化了。

Divergent Change(發散式變化)

你發現你想要修改的一個函數,卻必須同時修改諸多不相關的函數,例如,當你想要添加一個新的產品類型,你需要同步修改對產品查找,顯示,排序的函數。 有以上這些情況的話,就需要優化代碼了   針對某一外界 變化的所有相應修改,都只應該發生在單一class中,而這個新class內的所有內容都應該反應該外界變化。 通過提煉類的方式,找出因著某特定原因而造成的所有變化,獨立類。 問題原因: 通常,這種發散式修改是由於編程結構不合理或者“複製-粘貼式編程”。

優化思路

運用提煉類拆分類的行為。 如果不同的類有相同的行為,你可以考慮通過繼承來合併類和提煉子類。 效果: 提高代碼組織結構 減少重覆代碼  

Shotgun Surgery(散彈式修改)

-- 註意霰彈式修改 與 發散式變化 區別 : 發散式變化是在一個類受多種變化影響, 每種變化修改的方法不同, 霰彈式修改是 一種變化引發修改多個類中的代碼;

優化思路

1、代碼集中到某個類中 : 使用 Move Method(搬移函數) 和 Move Field(搬移欄位) 把所有需要修改的代碼放進同一個類中; 2、 代碼集中到新創建類中 : 沒有合適類存放代碼, 創建一個類, 使用 Inline Class(內聯化類) 方法將一系列的行為放在同一個類中; 3、造成分散式變化 : 上面的兩種操作會造成 Divergent Change(分散式變化), 使用Extract Class 處理分散式變化;  

Feature Envy(依戀情結)

函數對某個class的興趣高過對自己所處之 class的興趣。無數次經驗里,我們看到某個函數 為了計算某值,從另一個對象那兒調用幾乎半打的取值函數。 影響:數據和行為不在一處,修改不可控。 解決方案:讓數據和行為在一起,通過 Extract Method(提煉函數)和Move Method(搬移函數)的方法來處理,這函數到該去的地方。 例子:參考一個優秀博主提供的例子 https://blog.csdn.net/wxr0323/article/details/7884168

優化思路

1、函數全部數據來自另外一個類       做法:將數據提煉到一個獨立函數中  Move method。 2、函數部分數據來自另外一個類       做法:將“部分數據”提煉到一個函數中 Move method。 3、函數的數據來自不同類       做法:將數據分類,分別提煉各自的獨立的函數,在將這些函數移到各自屬於的類中。  

Data Clumps(數據泥團)

數據泥團指的是經常一起出現的數據,比如每個方法的參數幾乎相同,處理方式與過長參數列的處理方式相同,用Introduce Parameter Object(引入參數對象)將參數封裝成對象。

優化思路

1、觀察經常一起出現的數據; 2、通過提煉類的方法,放到屬於他們的對象中; 3、用對象來代替這些數據; 4、編譯測試。 eg:未優化代碼 例子參考一個優秀博主提供的例子 https://blog.csdn.net/geniusxi/article/details/78581542
public class Car{
    // 賓士
    public void printBenz(String brand, String model, Integer price, double power) {
        printBasicInfo(brand, model, price, power);
        getTax(power, price);
    }

    // 寶馬
    public void printBmw(String brand, String model, Integer price, double power) {
        printBasicInfo(brand, model, price, power);
        getTax(power, price);
    }

    // 提煉列印基本信息方法
    private void printBasicInfo(String brand, String model, Integer price, double power) {
        System.out.println("品牌" + brand);
        System.out.println("型號:" + model);
        System.out.println("動力:" + power);
        System.out.println("價格:" + price);
    }

    // 提煉計算稅費的方法
    private double getTax(double power, Integer price){
        double salePrice = price;
        if (price > 200000) {
            salePrice = price * 0.98;
        }

        if (power <= 1.6) {
            return salePrice * 0.05;
        } else {
            return salePrice * 0.1;
        }
    }
}
優化1,2步驟 上面代碼方法中,我們發現方法的參數大致相同,這時候我們可以用參數對象化來處理。
public class CarEntity {

    private String brand;
    private String model;
    private Integer price;
    private Double power;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Double getPower() {
        return power;
    }

    public void setPower(Double power) {
        this.power = power;
    }

}
優化3,4步驟
// 賓士
    public void printBenz(CarEntity carEntity) {
        printBasicInfo(carEntity);
        // 計算稅費
        getTax(carEntity);
    }

    // 寶馬
    public void printBmw(CarEntity carEntity) {
        printBasicInfo(carEntity);
        getTax(carEntity);
    }

    private void printBasicInfo(CarEntity carEntity) {
        System.out.println("品牌" + carEntity.getBrand());
        System.out.println("型號:" + carEntity.getModel());
        System.out.println("動力:" + carEntity.getPower());
        System.out.println("價格:" + carEntity.getPrice());
    }
    // 計算稅費
    private double getTax(CarEntity carEntity) {
        // 打折後價格
        double salePrice = carEntity.getPrice();
        if (carEntity.getPrice() > 200000) {
            salePrice = carEntity.getPrice() * 0.98;
        }

        if (carEntity.getPower() <= 1.6) {
            return salePrice * 0.05;
        } else {
            return salePrice * 0.1;
        }
    }
經過以上的優化,代碼就更加健壯了。  

Primitive Obsession(基本型別偏執)

寫代碼時總喜歡用基本類型來當參數,而不喜歡用對象。當要修改需求和擴展功能時,複雜度就增加了。

優化思路

1、如果你有一組應該總是被放在一起的屬性或參數,可以用提煉類的方式來處理; 2、如果你在參數列中看到多個基本型數據,可以引用參數對象; 3、如果你發現自己正從array中挑選數據,可以用對象取代數組。 優化思路1和2之前的例子說明瞭很多次,不再重覆。 優化思路3 用對象取代數組:你有一個數組(array),其中的元素各自代表不同的東西。就可以用對象來表示數組。 eg:
String[] row = new String[3];
  row [0] = "Liverpool";
  row [1] = "15";

//對象取代數組
  Performance row = new Performance();
  row.setName("Liverpool");
  row.setWins("15");
Performance 對象里包含name屬性和wins屬性,且這兩個屬性被定義為private ,同時擁有get方法和set方法。

Switch Statements(switch驚悚現身)

面向對象程式的一個最明顯特征就是:少用switch (或case)語句。從本質上說, switch語句的問題在於重覆(duplication)。

優化思路

這種情況我們可以引用工廠 + 策略模式。用工廠把重覆的switch提煉到一起構建成一個工廠類,策略模式把switch分支中執行的動作提煉成單獨的類。 例子參考一個優秀博主提供的例子 https://blog.csdn.net/geniusxi/article/details/78581542   更多精彩內容,請等待後續更新。 此文章也同步到了:https://blog.csdn.net/shi_hong_fei_hei/article/details/80519241
***************************************************************************

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

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

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

-Advertisement-
Play Games
更多相關文章
  • RPC傳輸 作為AMQP的實現,RabbitMQ使用RPC(remote procedure call)模式進行遠程會話。而不同於一般的RPC會話——客戶端發出指令,服務端響應,但服務端不會向客戶端發出指令;在AMQP規範中,服務端與客戶端皆會發出指令。 對於AMQP,客戶端首先發送protocol ...
  • 5月又即將要離我們遠去了,這個月有小長假51勞動節,有54青年節,有513母親節,更有坑爹的520神馬節?!! 廢話不說,又到了總結上個月乾貨的時候了,這個月我們帶來了各種Java技術乾貨,都是不得不看的 Java 實戰經驗及最新的熱門資訊。如果你有錯過本月乾貨,那麼你可以在這裡統一回顧一下。 "J ...
  • Java開源生鮮電商平臺-源碼地址公佈與思考和建議 說明:今天是承諾給大家的最後一天,我公佈了github地址(QQ群裡面有)。誠然這個是我的計劃中的事情,但是有以下幾點思考請大家共勉: 1. 你下了那麼多的github代碼,你學了多少呢?你又學會了多少呢? github的確是21世紀IT研發人員的 ...
  • 前言 在單體架構的秒殺活動中,為了減輕DB層的壓力,這裡我們採用了Lock鎖來實現秒殺用戶排隊搶購。然而很不幸的是儘管使用了鎖,但是測試過程中仍然會超賣,執行了N多次發現依然有問題。輸出一下代碼吧,可能大家看的比較真切: 代碼寫在service層,bean預設是單例的,也就是說lock肯定是一個對象 ...
  • Redis 概述 在我們日常的Java Web開發中,無不都是使用資料庫來進行數據的存儲,由於一般的系統任務中通常不會存在高併發的情況,所以這樣看起來並沒有什麼問題,可是一旦涉及大數據量的需求,比如一些商品搶購的情景,或者是主頁訪問量瞬間較大的時候,單一使用資料庫來保存數據的系統會因為面向磁碟,磁碟 ...
  • https://www.cnblogs.com/taoweiji/archive/2012/12/14/2818787.html GridBagLayout是java裡面最重要的佈局管理器之一,可以做出很複雜的佈局,可以說GridBagLayout是必須要學好的的, GridBagLayout 類是 ...
  • 對於volatile型變數的特殊規則 關鍵字volatile可以說是Java虛擬機提供的最輕量級的同步機制。 在處理多線程數據競爭問題時,不僅僅是可以使用synchronized關鍵字來實現,使用volatile也可以實現。 Java記憶體模型對volatitle專門定義了一些特殊的訪問規則,當一個變 ...
  • 承接上篇文章Django Rest Framework源碼剖析(二) 許可權,當服務的介面被頻繁調用,導致資源緊張怎麼辦呢?當然或許有很多解決辦法,比如:負載均衡、提高伺服器配置、通過代理限制訪問頻率等,但是django rest framework自身就提供了訪問頻率的控制,可以從代碼本身做控制。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...