<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 代碼重構:類重構的 8 個小技巧

    代碼重構:類重構的 8 個小技巧

    在大多數 OOP 類型的編程語言和面向對象程序設計中,根據業務建模主要有以下幾個痛點 ??:

    1. 對象不可能一開始就設計的合理,好用
    2. 起初就算設計精良,但隨著版本迭代,對象的職責也在發生變化
    3. 在迭代中,對象的職責往往會因為承擔過多職責,開始變的臃腫不堪(??聞到腐爛的味道了~)

    那么怎么解決以上的問題?就要運用一些重構的技巧,來讓代碼結構保持整潔,從而讓后續的需求擴展更加穩定

    1:合理的分配函數

    說明:從 OOP 的角度來考慮,如果函數之間頻繁的調用,顯然適合放在一個對象當中
    使用場景:在 A 對象內,看到它經常調用 B 類的函數,那么你就有必要需要考慮把 B 類的函數搬過來了。

    示例一

    空說很難理解,我們先展示一段代碼,來展示說項重構的手法:

    public class Account {
        // 計算透支費用
        double overdraftCharge() {
            if (_type.isPremium()) {
                double result = 10;
                if (_daysOverdrawn > 7) {
                    result += (_daysOverdrawn - 7) * 0.85;
                }
                return result;
            } else {
                return _daysOverdrawn * 1.75;
            }
        }
    
        double bankCharge() {
            double result = 4.5;
            if (_daysOverdrawn > 0) {
                result += overdraftCharge();
            }
            return result;
        }
    
        // 編碼道德 758 條:私有變量應該放在類的底部
        private AccountType _type;
        private int _daysOverdrawn;
    }
    
    // 賬戶類型
    class AccountType {
      //... do something 
    }
    

    在上面例子 ?? 中,我們看到 Account 顯然承擔一些不屬于它本身的職責,從 _type.isPremium() 的調用方式來看,overdraftCharge 不論從調用,還是命名來看,都更像是 AccountType 的職責,所以我們嘗試來搬遷它,最終代碼如下:

    class AccountType {
        // 從 Account 搬過來了
        double overdraftCharge(Account account) {
            if (isPremium()) {
                double result = 10;
                if (account.getDaysOverdrawn() > 7) {
                    result += (account.getDaysOverdrawn() - 7) * 0.85;
                }
                return result;
            } else {
                return account.getDaysOverdrawn() * 1.75;
            }
        }
        // more ...
    }
    
    public class Account {
        double bankCharge() {
            double result = 4.5;
            if (_daysOverdrawn > 0) {
                // 還可以根據不同 Account 類型進行擴展
                result += _type.overdraftCharge(this);
            }
            return result;
        }
    }
    

    函數 overdraftCharge 搬家后,我們有幾個可見的好處如下:

    1. 可以根據不同 Account 類型,計算不同結果,程序更靈活,調用方無需知道計算細節
    2. 避免類似 _type.isPremium() 的函數調用出現,看上去更合理

    總結

    通過 示例一 我們可以得出總結:

    1. 如果一個對象有太多行為和另一個對象耦合,那么就要考慮幫它搬家
    2. 只要是合理的分配函數,就可以使系統結構,對象本身的行為更加合理

    2:合理分配字段

    說明:這里的思路和 合理的分配函數 非常的相似,只是主體由 函數 替換為的 字段
    使用場景:當 A 類的某一個字段頻繁的被 B 類使用,那么就要考慮把它搬遷放到 B 類中

    示例一

    這里比較簡單,能理解上面的函數分配,也就能理解這里,我們看一段簡單的示例就好,還是以剛才的 Account 類為例子:

    public class Account {
        // 結算日息
        double interestForAmountDays (double amount,int days){
            return _interestRate * amount * days / 365;
        }
        private AccountType _type;
        // 利率 %
        private int _interestRate;
    }
    
    // 賬戶類型
    class AccountType {
        // do something....
    }
    

    從示例上看,_interestRate 字段顯然更適合放在 AccountType,我們做一次字段搬遷,搬完后代碼如下:

    public class Account {
        double interestForAmountDays (double amount,int days){
            return _type.getInterestRate() * amount * days / 365;
        }
        private AccountType _type;
    }
    
    // 賬戶類型
    class AccountType {
        // 利率 %
        private int _interestRate;
    
        public int getInterestRate() {
            return _interestRate;
        }
    }
    

    主要做有 2 個好處如下:

    1. 只需引入 AccountType 即可,無需再重復引入 _interestRate 字段
    2. AccountType 可以根據不同的類型,設置不同的 _interestRate 利率,代碼更靈活

    總結

    不管是搬遷函數,還是搬遷字段也好,它們都是在不斷重構類的職責和屬性,程序會跟隨需求不斷變化,沒有任何設計是可以保持一成不變的,所以這里的重構方法,不需要等到特定的時間和特定的規劃再去進行,重構應該是融入在日常開發當中,隨時隨地都在進行的

    3:拆解大類

    說明:隨著需求越來越多,原來設計的對象承擔的職責也會不斷的增多(方法,屬性等……),如果不加以使用重構的手段來控制對象的邊界(職責,功能),那么代碼最終就會變得過于復雜,難以閱讀和理解,最終演化成技術債,代碼腐爛,從而導致項目最終的失敗。
    使用場景:當一個類變的過于龐大,并且承擔很多不屬于它的職責(通過類名來辨識)的時候,創建新類的分擔它的工作

    示例一

    這里的 Person 承擔的過多的職責,我們把不屬于它職責范圍的函數抽離出來,從而保證對象上下文的清晰,拆解過程如下:
    Person Class

    實際代碼如下:

    public class Person {
        
        private String name;
        private String sex;
        private String age;
        private String officeAreaCode;
        private String officeNumber;
    
        //... 省略 get/set 代碼...
    }
    

    Person 的類圖看起來是這樣的:
    Person Class
    顯然 Person 做了很多不屬于自己的事情(現實情況往往要慘的多),想要分解的 Person 的話,我們可以這樣做:

    1. 識別 Person 的職責,然后創建一個 TelePhoneNumber 對象進行分擔
    2. 將關聯字段和函數遷移到 TelePhoneNumber 類中
    3. 進行單元測試

    當我們拆解后,新建的 TelePhoneNumber 類代碼如下:

    public class TelePhoneNumber {
    
        private String officeAreaCode;
        private String officeNumber;
    
        //... 省略 get/set 代碼...
    }
    

    這時候 Person 對象的職責就簡單和清晰很多了,對象結構如下:
    Person Class
    TelePhoneNumber 對接結構圖如下:
    Person Class

    總結

    拆解大類,是常見的重構技術手段,其最終目的都是保證每個對象的大小,職責都趨向合理。就像我們工作中如果有一個人太忙,那么就找一個人幫他分擔就好了。

    4:合并小類

    說明:這里是和 拆解大類 邏輯完全相反的的技巧
    說用場景:如果一個類沒有做太多的事情,就要考慮把它和相似的類合并在一起,這樣做的目的是:

    • 盡可能保證和控制每個類的職責在一個合理的范圍之內
    • 類過大就使用 拆解大類 的手法
    • 類太小就使用 合并小類 的手法

    示例一

    我們還是用上面的 PersonTelePhoneNumber 類舉例,合并過程如下:

    Person Class

    上圖可以看到 Person 在本身屬性很少的情況下,又拆分了 TelePhoneNumber 類,這屬于典型的過度拆分了。就需要使用合手法,將散亂在各地臨散的類進行合并。代碼如下:

    class Person {
    
        // Person 職責很少,沒必要拆解為 2 個類
        private String name;
        private String age;
    
        // ...
    }
    
    class TelePhoneNumber {
    
        private String phoneNumber;
    
        // ...
    }
    

    我們把 PersonTelePhoneNumber 進行合并,然后可以移除 TelePhoneNumberPerson 的最終代碼如下:

    public class Person {
    
        // Person 看上去更加合理了
        private String name;
        private String age;
        private String phoneNumber;
    
        // ... do some 
    }
    

    總結

    如果類很小,那么就要考慮將它合并,從而讓臨近的類的職責更加合理

    5:隱藏委托關系

    說明:委托關系是指,必須通過 A 類才能調用另一個 B 類對象
    使用場景:當只有個別函數需要通過關聯方式獲取的時候,使用隱藏委托模式,讓調用關系更加簡單

    示例一

    我們先看看委托模式的代碼,我們使用一個 PersonDepartment 類來舉例,代碼如下:

    public class Person {
    
        Department department;
    
        // 獲取所屬部門
        public Department getDepartment() {
            return department;
        }
    }
    
    class Department {
    
        private String chargeCode;
        private Person manage;
    
        public Department(Person manage) {
            this.manage = manage;
        }
    
        // 需要通過 Department 才能找到部門 Manage
        public Person getManage() {
            return manage;
        }
    }
    

    以上代碼設計看上去沒有問題,但是當我想要獲取某一個 Person 對象的所屬經理的時候,我就需要先獲取 PersonDepartment 對象,然后在 Department 中才能調用 getManager() 函數,代碼看起來就會很別扭,如下:

    Person john = new Person();
    // 委托模式:需要通過 Department 委托對象才能獲取 Person 想要的數據
    Person manage = john.getDepartment().getManage();
    

    這樣的類結構設計會存在以下幾個問題:

    1. 違背 OOP 的封裝原則,封裝的原則意味類盡可能的少對外的暴露信息
    2. 調用方需要去理解 PersonDepartment 的依賴關系,才能拿到 getManage() 信息
    3. 如果委托關系發生變化,那么調用方也需要修改代碼

    我們可以在 Person 中隱藏這層委托關系,從而讓 Person 可以直接獲取 getManage(),我們在 Person 加入以下代碼:

    public class Person {
    
        Department department;
    
        public Person getManage() {
            return department.getManage();
        }
    }
    

    這里看到 Person 有兩處修改:

    1. 隱藏 department.getManage() 委托關系
    2. 移除 getDepartment() 函數

    最終獲取 Person 的 getManage() 顯示更加直接,代碼如下:

    // 然后改用新函數獲取 Person 的 Manage 
    Person manage = john.getManage();
    

    總結

    如果 只有少數函數 需要依賴委托關系獲取的時候,可以使用 隱藏委托關系 的重構手法來讓類關系和調用變的簡單。

    6:移除中間人

    說明:這是 隱藏委托關系 這個技巧的反例
    使用場景:當有過多函數需要委托的時候,不建議繼續隱藏委托模式,直接讓調用方調用目標類,代碼反而更簡潔

    示例一

    我們上面的代碼通過在 Person 建立隱藏的委托模式,如下:

    public Person getManage() {
        return department.getManage();
    }
    

    通過這種方式來簡化對象關系的調用,但是再想想,在后續的需求迭代中,Department 也在不斷增加特性,如果 Department 每個新增的特性,都需要通過 Person 來進行委托的話,那么代碼看起來就像這樣:

    class Department {
    
        private String chargeCode;
        private Person manage;
        // 部門不斷發展,新增不少角色
        private Person captain;
        private Person groupLeader;
    
        // 省略獲取部門角色的方法....
    }
    

    所以每當 Department 增加角色,Person 都要修改代碼來繼續隱藏委托模式,Person 代碼如下:

    class Person {
    
        Department department;
    
        public Person getManage() {
            return department.getManage();
        }
        
        // 兼容 Department 新增的委托模式
        public Person getCaptain() {
            return department.getCaptain();
        }
    
        public Person getGroupLeader() {
            return department.getGroupLeader();
        }
    }
    

    所以 當有過多函數委托 的時候,倒不如移除 Person 這個簡單的中間人,讓對象直接調用 Department,區別如下:

    // 通過委托模式獲取
    Person manage = john.getManage();
    Person captain = john.getCaptain();
    Person groupLeader = john.getGroupLeader();
    
    // 移除中間人獲取
    Person manage = john.getDepartment().getManage();
    Person captain = john.getDepartment().getCaptain();
    Person groupLeader = john.getDepartment().getGroupLeader();
    

    這樣做的好處就是 Person 的代碼量大大減少,移除中間人后的 Person 類:

    public class Person {
        
        // 當委托工作變的非常重的時候,解除委托關系,可以讓 Person 獲得解放
        Department department;
    
        public Department getDepartment() {
            return department;
        }
    }
    

    總結

    • 當需要委托的特性越多,隱藏委托模式就顯得沒有必要,直接調用提供人代碼會更簡單
    • 如果只有簡單的委托特性,建議使用隱藏委托關系

    7:擴展工具類

    使用場景:當系統工具庫無法滿足你需求的時候,但是你又無法修改它(例如 Date 類),那么你可以封裝和擴展它,來讓它具備你需要的新特性。

    示例一

    例如我們有一段處理時間的函數,Date 工具類似乎并沒有提供這樣的方法,我們自己實現了,代碼如下:

    Date previousEnd = new Date();
    Date newStart = new Date(previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1);
    

    以上 newStart 實現的方式有以下幾個問題:

    • 表達式難以閱讀
    • 無法復用

    我們使用 擴展工具類 的方式,可以把程序重構為以下這樣:

    Date previousEnd = new Date();
    Date newStart = nextDay(previousEnd);
    
    // 提煉一個函數,作為 Date 類的擴展函數方法
    public static Date nextDay(Date arg) {
        return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
    }
    

    總結

    • 通過擴展工具類,為工具類增強更多的功能,從而滿足業務的需求
    • 如果有可能(獲取修改工具類的權限),那么可以考慮把擴展函數搬到工具類內部,讓更多人復用

    8:增強工具類

    使用場景:當你無法修改工具類(通常都無法修改),并且只有個別函數需要擴展的時候,那么使用 擴展工具類 沒有任何問題,只要少量的代碼就可以滿足功能需求,但是這種擴展是一次性的,例如擴展的 nextDay() 函數,無法被其他類復用。所以我們需要用增強工具類來解決這個問題

    示例一

    我們還是使用上面的 nextDay() 擴展函數來舉例,假如這個函數會經常被用到,那么我們就需要增強它,做法如下:

    1. 新建一個擴展類,然后繼承工具類(例如 Date
    2. 在擴展類內實現擴展函數,例如 nextDay()

    代碼如下:

    public class StrongDate extends Date {
        // 提煉一個函數,作為 Date 類的擴展函數方法
        public static Date nextDay(Date arg) {
            return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
        }
    
        // ... 這里還可以做更多擴展
    }
    

    調用方使用方式:

    Date previousEnd = new Date();
    Date newStart = StrongDate.nextDay(previousEnd);
    

    總結

    • 工具類的擴展函數會經常被復用,建議使用 增強工具類 的方式重構顯然更加的合適
    posted @ 2021-10-12 17:32  小二十七  閱讀(680)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看