1. 程式人生 > >重構 -改變既有程式碼的設計 ---- 筆記

重構 -改變既有程式碼的設計 ---- 筆記

目錄

這是一篇《重構 》的總結 ,我在學習的同時並使用它作為參考。這不是一本書的替代品,所以你要想真的想學習裡面的內容,買一本書使用這個文章作為參考和指南。

另外: 建議 評論 還 PR 都是十分歡迎的

1. TABLE OF CONTENT

3. BAD SMELLS IN CODE(程式碼的壞味道)

1. Duplicated code (重複的程式碼)

多個地方使用相同的程式碼

2. Long Method(很長的方法)

一個很長的程式是很難被理解的

3. Large Classes(超級大的類)

當一個類變的越來越大的時候,是難以閱讀的

4. Long Parameter List(長引數列表)

長引數是很難去理解,不符合也較難使用

5. Divergent Change(發散的改變)

當一個類經常因為不同原因發生在不同的方向發生變化

6. Shotgun Surgery(散彈槍修改)

每次你做一小部分修改時,都不的不需要做大量的修改在很多不同的類中

7. Feature Envy(依戀情結)

某個方法似乎在另外的類的興趣高於自己所處的類

8. Data Clumps(資料泥團)

一堆資料雜糅在一起(欄位, 引數)

9. Primitive Obsession(基本型別偏執)

使用基本型別替代小物件

10. Switch Statements(Switch 驚悚現身)

當程式中出現很多 switch 語句在很多地方,使用多型來進行替換

11. Parallel Inheritance Hierarchies(平行繼承類)

每當你為一個類增加一個子類,你不得不為另一個類增加相應的一個子類

12. Lazy Class(冗餘類)

當一個類不足與為其自身買單它就應該被刪除

13. Speculative Generality(誇誇其談未來性)

所有的鉤子和特殊情況處理那些不需要的

14. Temporary Field(令人迷惑的臨時變數)

一個臨時變數僅僅為某種特殊情況做而定,這樣的程式碼讓人難以理解

15. Message Chain(過長的訊息鏈)

當一個類請求呼叫一個物件,但是這個類又在呼叫其他的方法

16. Middle Man(中間人)

當一個物件委託大部分功能,開發中可能會過度的使用委託模式,導致某個類中的方法大都委託給其他方法處理

17. Inappropriate Intimacy(不恰當的親密關係)

當兩個類過度的親密,需要將其拆散

18. Alternative Classes with Different Interfaces(異曲同工的類)

類的方法過度相似

19. Incomplete Library Class(不完美的庫)

當我們使用外部依賴庫時

20. Data Class(資料類)

不要操作資料類,我們通過封裝它的不變性

21. Refused Bequest(被拒絕的遺贈)

子類不想使用父類的方法

22. Comments(過多沒用的註釋)

當一個方法使用過度的註釋解釋其中的邏輯時,說明這個方法應該被重構了。

6. COMPOSING METHODS(重新組織函式))

1. Extract Method(提煉函式)

你可以將一些程式碼組合起來,然後放到一個方法中


    void printOwing(double amount) {
        printBanner();
        //print details
        System.out.println ("name:" + _name);
        System.out.println ("amount" + amount);
    }

to


    void printOwing(double amount) {
        printBanner();
        printDetails(amount);
    }

    void printDetails (double amount) {
        System.out.println ("name:" + _name);
    System.out.println ("amount" + amount);
    }

動機

  • 增加程式碼被複用的機會 * 閱讀方法就像閱讀一系列宣告一樣簡單

    void printOwing(double previousAmount) {
        Enumeration e = _orders.elements();
        double outstanding = previousAmount * 1.2;
        printBanner();

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

to


    void printOwing(double previousAmount) {
        printBanner();
        double outstanding = getOutstanding(previousAmount * 1.2);
        printDetails(outstanding);
    }

    double getOutstanding(double initialValue) {
        double result = initialValue;
        Enumeration e = _orders.elements();

        while (e.hasMoreElements()) {
            Order each = (Order) e.nextElement();
            result += each.getAmount();
        }
        return result;
    }

2. Inline Method (行內函數)

一個函式本體和函式名稱一樣容易理解


    int getRating() {
        return (moreThanFiveLateDeliveries()) ? 2 : 1;
    }

    boolean moreThanFiveLateDeliveries() {
        return _numberOfLateDeliveries > 5;
    }

to


    int getRating() {
        return (_numberOfLateDeliveries > 5) ? 2 : 1;
    }

動機

  • 當間接不是必要的時候
  • 當一組方法被嚴格的分解,會使這個方法變得清晰

3. Inline Temp (內聯臨時變數)

你申明瞭一個臨時的變數在一段表達裡面,然後臨時的變數將會阻擋你重構


    double basePrice = anOrder.basePrice();
    return (basePrice > 1000)

to


    return (anOrder.basePrice() > 1000)

Motivation

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

你正在使用臨時變數來儲存表示式的結果


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

to


    if (basePrice() > 1000){
        return basePrice() * 0.95;
    }
    else{
        return basePrice() * 0.98;
    }
    ...
    double basePrice() {
        return _quantity * _itemPrice;
    }

動機

  • 使用方法代替臨時變數,類中的任何方法都可以獲取資訊

5. Introduce Explaining Variable (引入解釋性變數)

有一個複雜的表示式


    if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
        (browser.toUpperCase().indexOf("IE") > -1) &&
        wasInitialized() && resize > 0 )
    {
        // do something
    }

to


    final boolean isMacOs = platform.toUpperCase().indexOf("MAC") >-1;
    final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") >-1;
    final boolean wasResized = resize > 0;
    if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
        // do something
    }

動機

  • 當一個表示式難以理解時

6. Split Temporary Variable (分解臨時變數)

你能有一個臨時變數宣告不止一次,但是它不是迴圈體中的變數或者要被儲存的變數


    double temp = 2 * (_height + _width);
    System.out.println (temp);
    temp = _height * _width;
    System.out.println (temp);

to


    final double perimeter = 2 * (_height + _width);
    System.out.println (perimeter);
    final double area = _height * _width;
    System.out.println (area);

動機

  • 變數不應該有多次的宣告
  • 使用臨時變數在兩次不同的地方,是閱讀者十分迷惑的

7. Remove Assignments to Parameters(移除對引數的賦值)

下的程式碼將對引數進行了賦值


    int discount (int inputVal, int quantity, int yearToDate) {
        if (inputVal > 50) {
            inputVal -= 2;
        }
    }

to


    int discount (int inputVal, int quantity, int yearToDate) {
        int result = inputVal;
        if (inputVal > 50) {
            result -= 2;
        }
    }

動機

  • 改變內部的物件時可以的,但是不能將這個物件指向別的物件
  • 引數的作用僅僅是表達傳遞物件

8. Replace Method with Method Object (以函式物件取代函式)

將這個函式放進一個單獨的物件,如此一來區域性變數就變成物件內部的欄位,然後你可以在同一個物件中將這個大型函式分解為多個小型函式


    class Order...
        double price() {
            double primaryBasePrice;
            double secondaryBasePrice;
            double tertiaryBasePrice;
            // long computation;
            ...
        }

to


    class Order...
        double price(){
            return new PriceCalculator(this).compute()
        }
    }

    class PriceCalculato...
    compute(){
        double primaryBasePrice;
        double secondaryBasePrice;
        double tertiaryBasePrice;
        // long computation;
        return ...
    }

動機

  • 當一個方法有很多的本地變數時進行分解時不容易的

它的樣本本不應該這樣的重構,但是為顯示這樣做的方法


    Class Account
        int gamma (int inputVal, int quantity, int yearToDate) {
            int importantValue1 = (inputVal * quantity) + delta();
            int importantValue2 = (inputVal * yearToDate) + 100;
            if ((yearToDate - importantValue1) > 100)
            importantValue2 -= 20;
            int importantValue3 = importantValue2 * 7;
            // and so on.
            return importantValue3 - 2 * importantValue1;
        }
    }

to


    class Gamma...
        private final Account _account;
        private int inputVal;
        private int quantity;
        private int yearToDate;
        private int importantValue1;
        private int importantValue2;
        private int importantValue3;

        Gamma (Account source, int inputValArg, int quantityArg, int yearToDateArg) {
            _account = source;
            inputVal = inputValArg;
            quantity = quantityArg;
            yearToDate = yearToDateArg;
        }

        int compute () {
            importantValue1 = (inputVal * quantity) + _account.delta();
            importantValue2 = (inputVal * yearToDate) + 100;
            if ((yearToDate - importantValue1) > 100)
            importantValue2 -= 20;
            int importantValue3 = importantValue2 * 7;
            // and so on.
            return importantValue3 - 2 * importantValue1;
        }

        int gamma (int inputVal, int quantity, int yearToDate) {
            return new Gamma(this, inputVal, quantity,yearToDate).compute();
        }

9. Substitute Algorithm (演算法替換)

你想更換一個更為清晰高效的演算法


    String foundPerson(String[] people){
        for (int i = 0; i < people.length; i++) {
            if (people[i].equals ("Don")){
                return "Don";
            }
            if (people[i].equals ("John")){
                return "John";
            }
            if (people[i].equals ("Kent")){
                return "Kent";
            }
        }
        return "";
    }

to


    String foundPerson(String[] people){
        List candidates = Arrays.asList(new String[] {"Don", "John","Kent"});
    for (int i = 0; i<people.length; i++)
        if (candidates.contains(people[i]))
            return people[i];
    return "";
    }

動機

  • 打破一些複雜的概念
  • 使演算法更容易修改
  • 替換一個大的複雜的演算法是十分困難的,讓演算法變的簡單更容易對演算法進行替換

7. Moving features between elements(移動物件)

10. Move method (移動方法)

在進行方法的初始定義的時候要想下以後會不會有其他的類也將會用到它

一個類它通常會建立一個新的簡單的方法體, 同時它會將9⃣舊的方法做一個簡單的委託或者移除它


    class Class1 {
        aMethod()
    }

    class Class2 {  }

to


    class Class1 {  }

    class Class2 {
        aMethod()
    }

動機

當一個類做了很多工作,或者這個類過度的耦合

11. Move field (移動欄位)

當一個欄位被定義的時候,可能不僅被不止一個類使用。 建立一個欄位在一個目標類中,然後改變所有的擁有者


    class Class1 {
        aField
    }

    class Class2 {  }

to


    class Class1 {  }

    class Class2 {
        aField
    }

動機

如果一個欄位被超過多個類引用

12. Extract Class (提取類)

你有一個類,但是這個類做了它份外的事情

建立一個新的類,然後將相關欄位移入到新的類中


    class Person {
        name,
        officeAreaCode,
        officeNumber,
        getTelephoneNumber()
    }

to


    class Person {
        name,
        getTelephoneNumber()
    }

    class TelephoneNumber {
        areaCode,
        number,
        getTelephoneNumber()
    }

動機

類隨著業務的增長在變化

在合適的時候進行分解它

  • 相似的方法組合在一起
  • 資料子集通常一起變化或者相互依賴

13. Inline Class (一致的類)

一個其實沒做多少事情的類

將這個類整合到另外一個類中,然後刪除這個類


    class Person {
        name,
        getTelephoneNumber()
    }

    class TelephoneNumber {
        areaCode,
        number,
        getTelephoneNumber()
    }

to


    class Person {
        name,
        officeAreaCode,
        officeNumber,
        getTelephoneNumber()
    }

動機

在重構的時候將這個類的基本資訊移入到另外一個類中,然後在移除這個類

14. Hide Delegate (隱藏委託)

客戶端其實呼叫的是物件的委託類 在服務端建立一個方法,然後隱藏這個委託類


    class ClientClass {
        //Dependencies
        Person person = new Person()
        Department department = new Department()
        person.doSomething()
        department.doSomething()
    }

to


    class ClientClass {
        Person person = new Person()
        person.doSomething()
    }

    class Person{
        Department department = new Department()
        department.doSomething()
    }

解決方法


    class ClientClass{
        Server server = new Server()
        server.doSomething()
    }

    class Server{
        Delegate delegate = new Delegate()
        void doSomething(){
            delegate.doSomething()
        }
    }
    //委託類其實隱藏在客戶類裡面
    // 改變不會傳播到客戶端那邊,因為它之後影響到服務端這邊
    class Delegate{
        void doSomething(){...}
    }

動機

關鍵在於封裝 類應該儘量的使用其他的類 > manager = john.getDepartment().getManager();




    class Person {
        Department _department;
        public Department getDepartment() {
            return _department;
        }
        public void setDepartment(Department arg) {
            _department = arg;
            }
        }

    class Department {
        private String _chargeCode;
        private Person _manager;
        public Department (Person manager) {
            _manager = manager;
        }
        public Person getManager() {
            return _manager;
        }
        ...

to

> manager = john.getManager();


    class Person {
        ...
        public Person getManager() {
            return _department.getManager();
        }
    }

15. Remove Middle Man (移除中間人)

一個類通過代理幹了太多的事情 讓客戶直接呼叫委託


    class ClientClass {
        Person person = new Person()
        person.doSomething()
    }

    class Person{
        Department department = new Department()
        department.doSomething()
    }

to


    class ClientClass {
        //Dependencies
        Person person = new Person()
        Department department = new Department()
        person.doSomething()
        department.doSomething()
    }

動機

當客戶類使用過多的中間人呼叫委託的方法

16. Introduce Foreign Method (引入外加的函式)

一個類是引用的外部開源包,但是不能修改其內部的邏輯 建立一個新的方法在這個類中,並以第一個引數的形式傳入一個服務類例項


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

to


    Date newStart = nextDay(previousEnd);

    private static Date nextDay(Date date){
        return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
    }

動機

當你使用一個類,這個類你又不能對其進行修改的時候可以採用這樣方式

17. Introduce Local Extension (引入本地擴充套件)

你需要為一個服務類提供一些額外的方法,但是你無法修改這個子類 建立一個新的類,使它包含這些額外的方法。這個擴充套件的類成為源類的子類或者包裝類


    class ClientClass(){

        Date date = new Date()
        nextDate = nextDay(date);

        private static Date nextDay(Date date){
            return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
        }
    }

to


    class ClientClass() {
        MfDate date = new MfDate()
        nextDate = nextDate(date)
    }
    class MfDate() extends Date {
        ...
        private static Date nextDay(Date date){
            return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
        }
    }

動機

8. ORGANIZING DATA (組織資料)

18. Self Encapsulate Field (對欄位獲取進行封裝)

你可以直接獲取物件,但是這樣的話會變得越來越複雜 通過建立setting getting 方法來獲取這些欄位


    private int _low, _high;
    boolean includes (int arg) {
        return arg >= _low && arg <= _high;
    }

to


    private int _low, _high;
    boolean includes (int arg) {
        return arg >= getLow() && arg <= getHigh();
    }
    int getLow() {return _low;}
    int getHigh() {return _high;}

動機

允許子類可以覆蓋如何get方法,並且這樣的話它更加的支援靈活的管理,例如延遲載入

19. Replace Data Value with Object (用物件替換資料值)

當你有個資料項需要進行新增資料或行為 將資料項轉換為物件


    class Order...{
        private String _customer;
        public Order (String customer) {
            _customer = customer;
        }
    }

to


    class Order...{
        public Order (String customer) {
            _customer = new Customer(customer);
        }
    }

    class Customer {
        public Customer (String name) {
            _name = name;
        }
    }

動機 簡單的資料物件並不簡單

20. Change Value to Reference (將值改為引用)

你有個類擁有很多單個物件,這些物件需要用一個單獨的物件替代 將這個物件轉換為引用物件


    class Order...{
        public Order (String customer) {
            _customer = new Customer(customer);
        }
    }

    class Customer {
        public Customer (String name) {
            _name = name;
        }
    }

to


    //Use Factory Method
    //使用工廠方法
    class Customer...
        static void loadCustomers() {
            new Customer ("Lemon Car Hire").store();
            new Customer ("Associated Coffee Machines").store();
            new Customer ("Bilston Gasworks").store();
        }
        private void store() {
            _instances.put(this.getName(), this);
        }
        public static Customer create (String name) {
            return (Customer) _instances.get(name);
        }

動機 引用物件是類似於消費者或者賬單這樣的物件,每個物件代表這一類物件在一個真實的世界,並使用物件表示來測試它們是否相同

21. Change Reference to Value (將引用改為值)

你有一個有一個引用物件是很小,不變,難以管理的 將其轉換為值物件


    new Currency("USD").equals(new Currency("USD")) // returns false

to


    new Currency("USD").equals(new Currency("USD")) // now returns true

動機 使用引用物件是變得越來月複雜,並且引用物件是不變和單一的。尤其在分散式和併發系統中

22. Replace Array with Object (用物件代替陣列)

你擁有一個數組,其中這些元素是不同的 使用一個物件來替換這個陣列,將陣列的元素賦值在物件的屬性上


    String[] row = new String[3];
    row [0] = "Liverpool";
    row [1] = "15";

to


    Performance row = new Performance();
    row.setName("Liverpool");
    row.setWins("15");

動機

陣列應該被用在一些相似的集合物件序列中

23. Duplicate Observed Data (監控資料物件)

可能你有一些domain資料是通過GUI控制的與此同時這些domain資料是需要訪問的 往一些實體物件複製一些資料,通過設定觀察者來同步兩部分資料

動機 為了將程式碼從使用者介面分解到業務處理層

24. Change Unidirectional Association to Bidirectional(將單向聯絡改為雙向聯絡)

你有兩個物件,這兩個物件需要使用對方的特徵屬性,但是目前只有一種連線方式 新增返回指標,然後更改修飾符已更改兩個物件


    class Order...
        Customer getCustomer() {
            return _customer;
        }
        void setCustomer (Customer arg) {
            _customer = arg;
        }
        Customer _customer;
    }

to


    class Order...
        Customer getCustomer() {
            return _customer;
        }
        void setCustomer (Customer arg) {
            if (_customer != null) _customer.friendOrders().remove(this);
            _customer = arg;
            if (_customer != null) _customer.friendOrders().add(this);
        }
        private Customer _customer;

        class Customer...
            void addOrder(Order arg) {
                arg.setCustomer(this);
            }
            private Set _orders = new HashSet();

            Set friendOrders() {
                /** should only be used by Order */
                return _orders;
            }
        }
    }

    // Many to Many
    class Order... //controlling methods
        void addCustomer (Customer arg) {
            arg.friendOrders().add(this);
            _customers.add(arg);
        }
        void removeCustomer (Customer arg) {
            arg.friendOrders().remove(this);
            _customers.remove(arg);
        }
    class Customer...
        void addOrder(Order arg) {
            arg.addCustomer(this);
        }
        void removeOrder(Order arg) {
            arg.removeCustomer(this);
        }
    }

動機 當物件引用需要互相引用的時候,你應該採用這種方法

25. Change Bidirectional Association to Unidirectional (將單向改為雙向的聯絡)

當你有個雙向聯絡的類,但是在後期一個類不在需要另一個類中的屬性了 扔掉不需要的聯絡

動機 當雙向聯絡不在需要,減少複雜度,移除殭屍物件,消除相互依賴

26. Replace Magic Number with Symbolic Constant (使用符號來替代魔法字串)

你有一個特定含義的字串

建立一個常量,名稱根據它的意思命名然後替換那個數字


    double potentialEnergy(double mass, double height) {
        return mass * 9.81 * height;
    }

to


    double potentialEnergy(double mass, double height) {
        return mass * GRAVITATIONAL_CONSTANT * height;
    }
    static final double GRAVITATIONAL_CONSTANT = 9.81;

動機 避免使用魔法數字

27. Encapsulate Field (封裝欄位)

這裡有一個公共的欄位 將它改為私有的並提供訪問函式


    public String _name

to


    private String _name;
    public String getName() {
        return _name;
    }
    public void setName(String arg) {
        _name = arg;
    }

動機 你應該將你資料公開

28. Encapsulate Collection (封裝集合)

一個返回幾個的方法 確保返回一個只讀的影像物件,然後提供新增和移除方法


    class Person {
        Person (String name){
            HashSet set new HashSet()
        }
        Set getCourses(){}
        void setCourses(:Set){}
    }

to


    class Person {
        Person (String name){
            HashSet set new HashSet()
        }
        Unmodifiable Set getCourses(){}
        void addCourses(:Course){}
        void removeCourses(:Course){}
    }

動機

  • 封裝減少了擁有類和和其客戶端的耦合
  • getter 方法不應該返回集合的本身
  • getter方法應返回對集合進行操作的內容並隱藏其中不必要的細節
  • 這個幾個不應該有setter方法,只能新增和移除操作

29. Remove Record with data class (在資料類中移除 記錄值)

你必須面對一個記錄值在傳統的程式設計環境中 使用一個殭屍資料物件代替記錄值

動機

  • 複製一個傳奇程式碼
  • 使用傳統的API程式設計和資料庫來代替一個記錄值

30. Replace Type Code with Class (使用類來替代類別程式碼)

一個類擁有數字類別碼,不能影響其行為 使用類來代替數字


    class Person{
        O:Int;
        A:Int;
        B:Int;
        AB:Int;
        bloodGroup:Int;
    }

to


    class Person{
        bloodGroup:BloodGroup;
    }

    class BloodGroup{
        O:BloodGroup;
        A:BloodGroup;
        B:BloodGroup; 
        AB:BloodGroup;
    }

動機 靜態類別檢查

31. Replace Type Code with Subclasses (使用子類來替代類別程式碼)

在一個類中擁有一個不變的型別碼影響整個類的行為 使用子類來替代這個不變的型別碼


    class Employee...
        private int _type;

        static final int ENGINEER = 0;
        static final int SALESMAN = 1;
        static final int MANAGER = 2;

        Employee (int type) {
            _type = type;
        }
    } 

to


    abstract int getType();
    static Employee create(int type) {
        switch (type) {
            case ENGINEER:
                return new Engineer();
            case SALESMAN:
                return new Salesman();
            case MANAGER:
                return new Manager();
            default:
                throw new IllegalArgumentException("Incorrect type code value");
        }
    }

動機

  • 執行不同的程式碼邏輯取決於這個type的值
  • 當每個type物件有著唯一的特徵
  • 應用於架構體

32. Replace Type Code with State/Strategy(通過狀態模式或者策略模式來代替型別碼)

在類中有個型別碼,並通過這個型別碼來影響行為,但是你不能使用子類 通過狀態物件來代替這個型別碼


    class Employee {
        private int _type;

        static final int ENGINEER = 0;
        static final int SALESMAN = 1;
        static final int MANAGER = 2;

        Employee (int type) {
            _type = type;
        }
        int payAmount() {
            switch (_type) {
                case ENGINEER:
                    return _monthlySalary;
                case SALESMAN:
                    return _monthlySalary + _commission;
                case MANAGER:
                    return _monthlySalary + _bonus;
                default:
                    throw new RuntimeException("Incorrect Employee");
                }
            }
        }
    }

to


    class Employee...
        static final int ENGINEER = 0;
        static final int SALESMAN = 1;
        static final int MANAGER = 2;

        void setType(int arg) {
            _type = EmployeeType.newType(arg);
        }
        class EmployeeType...
            static EmployeeType newType(int code) {
                switch (code) {
                    case ENGINEER:
                        return new Engineer();
                    case SALESMAN:
                        return new Salesman();
                    case MANAGER:
                        return new Manager();
                    default:
                        throw new IllegalArgumentException("Incorrect Employee Code");
                }
            }
        }
        int payAmount() {
            switch (getType()) {
                case EmployeeType.ENGINEER:
                    return _monthlySalary;
                case EmployeeType.SALESMAN:
                    return _monthlySalary + _commission;
                case EmployeeType.MANAGER:
                    return _monthlySalary + _bonus;
                default:
                    throw new RuntimeException("Incorrect Employee");
            }
        }
    }

動機

  • 31. Replace Type Code with Subclasses 是相似的,但是它可以使用在型別碼發生了改變在物件的生命週期發生了變化或者另一個原因阻止了子類的變化,則可以使用它
  • 它通常是和狀態模式或者策略模式配合使用

32. Replace Subclass with Fields(用欄位代替子類)

你的子類僅在返回常數變數資料變數的方法中有所不同 將這個方法提升到父類中,並移除這個子類


    abstract class Person {
        abstract boolean isMale();
        abstract char getCode();
        ...
    }
    class Male extends Person {
            boolean isMale() {
            return true;
        }
        char getCode() {
            return 'M';
        }
    }
    class Female extends Person {
        boolean isMale() {
            return false;
        }
        char getCode() {
            return 'F';
        }
    }

to


    class Person{
        protected Person (boolean isMale, char code) {
            _isMale = isMale;
            _code = code;
        }
        boolean isMale() {
            return _isMale;
        }
        static Person createMale(){
            return new Person(true, 'M');
        }
        static Person createFemale(){
            return new Person(false, 'F');
        }
    }

動機

  • 當子類的某個方法不足與繼續存在
  • 將這個子類徹底刪除,並將這個欄位上移到父類中
  • 刪除額外的子類

9. SIMPLIFYING CONDITIONAL EXPRESSIONS(簡化條件表示式)

33. Decompose Conditional (分解條件)

你有一個複雜的條件(大量的if else then ) 使用額外的方法代替這個表示式,將then 放在一部分,else 放在一部分


    if (date.before (SUMMER_START) || date.after(SUMMER_END))
        charge = quantity * _winterRate + _winterServiceCharge;
    else 
        charge = quantity * _summerRate;

to


    if (notSummer(date))
        charge = winterCharge(quantity);
    else 
        charge = summerCharge (quantity);

動機

  • 將條件表示式高亮,這樣的話你能清楚將其分開
  • 對分叉之後的結果進行高亮

34. Consolidate Conditional Expression


    double disabilityAmount() {
    if (_seniority < 2) return 0;
    if (_monthsDisabled > 12) return 0;
    if (_isPartTime) return 0;
    // compute the disability amount

to


    double disabilityAmount() {
    if (isNotEligableForDisability()) return 0;
    // compute the disability amount

35. Consolidate Duplicate Conditional Fragments (合併重複的條件片段)

在條件表示式的每個分支上有著相同的一片程式碼 將這段重複代搬移到條件表示式之外


    if (isSpecialDeal()) {
        total = price * 0.95;
        send();
    }
    else {
        total = price * 0.98;
        send();
    }

to


    if (isSpecialDeal()) {
        total = price * 0.95;
    }
    else {
        total = price * 0.98;
    }
    send();

動機 使得變數清晰並保持相同

36. Remove Control Flag (移除控制標記)

在一系列的布林表示式中,某個變數帶有“控制標記”的作用 已break或者return語句取代控制標記


    void checkSecurity(String[] people) {
        boolean found = false;
            for (int i = 0; i < people.length; i++) {
                if (! found) {
                    if (people[i].equals ("Don")){
                        sendAlert();
                        found = true;
                    }
                    if (people[i].equals ("John")){
                        sendAlert();
                        found = true;
                    }
                }
        }
    }

to


    void checkSecurity(String[] people) {
        for (int i = 0; i < people.length; i++) {
            if (people[i].equals ("Don")){
                sendAlert();
                break; // or return
            }
            if (people[i].equals ("John")){
                sendAlert();
                break; // or return
            }
        }
    }

動機

  • 控制標記的作用是在於決定是否繼續下面流程,但是現代語言注重於使用breakcontinue
  • 確保真實的條件表示式是清晰的

37. Replace Nested Conditional with Guard Clauses (以衛語句取代巢狀的條件表示式)

函式的條件邏輯使人難以看清正常的執行路徑 使用衛語句表現所有的特殊情況


    double getPayAmount() {
        double result;
        if (_isDead) result = deadAmount();
        else {
            if (_isSeparated) result = separatedAmount();
            else {
                if (_isRetired) result = retiredAmount();
                else result = normalPayAmount();
            };
        }
        return result;
    };

to


    double getPayAmount() {
        if (_isDead) return deadAmount();
        if (_isSeparated) return separatedAmount();
        if (_isRetired) return retiredAmount();
        return normalPayAmount();
    };

動機

  • 如果這個條件是非同尋常的條件,檢查這條件是否符合然後返回true
  • 這樣大度的檢查被稱為“衛語句”
  • 對某一條分支已特別的重,如果使用if-then-else 結構,你對if分支和else分支的重要性是同等的
  • 各個分支具有同一樣的重要性
  • 取代之前的觀念 “每個函式只能有一個入口和一個出口”

38. Replace Conditional with Polymorphism (以多型取代條件表示式)

你手上有一個條件表示式,它根據物件的型別的不同選擇不同的行為 將條件表示式的所有分支放進一個子類內的覆蓋函式中,然後將原始函式宣告為抽象函式


    class Employee {
        private int _type;

        static final int ENGINEER = 0;
        static final int SALESMAN = 1;
        static final int MANAGER = 2;

        Employee (int type) {
            _type = type;
        }
        int payAmount() {
            switch (_type) {
                case ENGINEER:
                    return _monthlySalary;
                case SALESMAN:
                    return _monthlySalary + _commission;
                case MANAGER:
                    return _monthlySalary + _bonus;
                default:
                    throw new RuntimeException("Incorrect Employee");
                }
            }
        }
    }

to


    class Employee...
        static final int ENGINEER = 0;
        static final int SALESMAN = 1;
        static final int MANAGER = 2;

        void setType(int arg) {
            _type = EmployeeType.newType(arg);
        }
        int payAmount() {
            return _type.payAmount(this);
        }
    }

    class Engineer...
        int payAmount(Employee emp) {
            return emp.getMonthlySalary();
        }
    }

動機

  • 如果物件的行為因其型別而異,請避免編寫顯示的條件
  • Switch 宣告在面嚮物件語言中應該儘量少的被使用

39. Introduce Null Object (引入Null 物件)

你不得不檢查物件是否為Null物件 將null值替換為null物件


    if (customer == null){
        plan = BillingPlan.basic();
    } 
    else{
        plan = customer.getPlan();
    }

to


    class Customer {

    }

    class NullCusomer extends Customer {

    }

動機

  • 物件根據其型別做正確的事情,Null物件也應該遵守這個規則

40. Introduce Assertion (引入斷言)

某段程式碼需要對程式狀態做出某種假設 已斷言明確表現這種假設


    double getExpenseLimit() {
        // should have either expense limit or a primary project
        return (_expenseLimit != NULL_EXPENSE) ?
            _expenseLimit:
            _primaryProject.getMemberExpenseLimit();
    }

to


    double getExpenseLimit() {
        Assert.isTrue (_expenseLimit != NULL_EXPENSE || _primaryProject != null);
        return (_expenseLimit != NULL_EXPENSE) ?
            _expenseLimit:
            _primaryProject.getMemberExpenseLimit();
    }

動機

  • 斷言是一種明確表達會為true的行為
  • 當斷言失敗時往往是一個非受控的異常
  • 斷言往往在生產程式碼中移除
  • 交流層面 : 斷言能使程式的閱讀者理解程式碼所做的假設
  • 在除錯的角度 : 斷言能使程式設計者儘可能近的接觸bug

10. MAKING METHOD CALLS SIMPLER (簡化函式的呼叫)

41. Rename method (函式改名)

函式的名稱不能表達函式的用途 修改函式名稱

    getinvcdtlmt()

to

    getInvoiceableCreditLimit

動機 函式的名稱最好能表達函式的意圖

42. Add Parameter (新增引數)

某個函式需要從呼叫端得到更多的資訊 為此函式新增一個物件函式,讓改物件帶進函式所需要資訊

    getContact()

to

    getContact(:Date)

動機 在改變方法之後,你獲得更多的資訊

43. Remove Parameter(移除引數)

一個引數不在函式中使用了 移除它

    getContact(:Date)

to

    getContact()

動機 一個引數不再使用還留著它幹嘛?

44. Separate Query from Modifier (將查詢函式和修改函式分離)

某個函式即返回函式的狀態值,又修改物件的狀態 建立兩個不同的函式,其中一個負責查詢,另一個負責修改

    getTotalOutStandingAndSetReadyForSummaries()

to

    getTotalOutStanding()
    SetReadyForSummaries()

動機 將有副作用的方法和沒有副作用的方法分開

45. Parameterize Method (令函式攜帶引數)

若干函式做了類似的工作,但在函式本體中卻包含了不同的值 建立單一函式,已引數表達那些不同的值

    fivePercentRaise()
    tenPercentRaise()

to

    raise(percentage)

動機 移除重複的程式碼提高靈活度

46. Replace Parameter with Explicit Methods (已明確函式取代引數)

?一個函式,其中完全取決於引數值而採取不同的行為 針對該函式的每一個可能值,建立一個獨立函式


    void setValue (String name, int value) {
        if (name.equals("height")){}
            _height = value;
            return;
        }
        if (name.equals("width")){
            _width = value;
            return;
        }
        Assert.shouldNeverReachHere();
    }

to


    void setHeight(int arg) {
        _height = arg;
    }
    void setWidth (int arg) {
        _width = arg;
    }

動機

  • 避免條件行為
  • 在編譯器進行檢查
  • 清晰的介面

47. Preserve Whole Object (保持物件的完整)

你從某個物件支行取出若干值,將他們作為某一次函式呼叫時的引數 改為傳遞一整個物件


    int low = daysTempRange().getLow();
    int high = daysTempRange().getHigh();
    withinPlan = plan.withinRange(low, high);

to


    withinPlan = plan.withinRange(daysTempRange());

動機

  • 使引數列表在變化的時候具有魯棒性
  • 使程式碼更與易讀
  • 在程式碼中移除相似的重複的程式碼
  • 消極的 : 增加了函式引數在呼叫時的依賴

48. Replace Parameter with Method (已函式取代引數)

物件呼叫某個函式,並將其所得的結果作為引數,傳遞給另一個函式。而接受該引數的函式本身也能夠呼叫錢一個函式 將函式接受者去除該項引數,並直接呼叫前一個函式


    int basePrice = _quantity * _itemPrice;
    discountLevel = getDiscountLevel();
    double finalPrice = discountedPrice (basePrice, discountLevel);

to


    int basePrice = _quantity * _itemPrice;
    double finalPrice = discountedPrice (basePrice);

動機

  • 如果一個函式可以通過其他的途徑獲得引數值,那麼它就不應該通過引數取得該值
  • 如果你能有其他的方法或者相同的計算值
  • 如果要在對呼叫物件的引用的其他物件上呼叫方法

49. Introduce Parameter Object (引入引數物件)

某些引數總是很自然的同時出現 以一個物件取代這些引數


    class Customer{
        amountInvoicedIn (start : Date, end : Date)
        amountReceivedIn (start : Date, end : Date)
        amountOverdueIn (start : Date, end : Date)
    }

to


    class Customer{
        amountInvoicedIn (: DateRange)
        amountReceivedIn (: DateRange)
        amountOverdueIn (: DateRange)
    }

動機

  • 減少引數列表的長度
  • 使程式碼看的更加簡潔

50. Remove Setting Method (移除設值函式)

類中的某個欄位應該在物件建立時被設值,然後就不再改變 去掉該欄位的所有設值函式


    class Employee{
        setImmutableValue()
    }

to


    class Employee{
        ¯\_(ツ)_/¯
    }

動機 確保你的清晰的目的 : 如果你想你的欄位在建立之後就不要被改變了,你就不應該提供一個setting方法用於確保你的欄位是否不被改變的

51. Hide Method (隱藏函式)

有一個函式,從來沒有被其他任何類用到 將這個函式修改為 private


    class Employee{
        public method()
    }

to


    class Employee{
        private method()
    }

動機 如果一個方法不需要被外部呼叫,那麼就應該講這個方法隱藏

52. Replace Constructor with Factory Method (已工廠函式取代建構函式)

在建立物件時不僅僅是做簡單的健夠動作 將建構函式替換為工廠函式


    Employee (int type) {
        _type = type;
    }

to


    static Employee create(int type) {
        return new Employee(type);
    }

建立一個物件依賴於其子類,建構函式只能返回單一型別的物件,因此你需要將建構函式替換為一個工廠函式

53. Encapsulate Downcast (封裝向下轉型)

某個函式返回的物件,需要由呼叫者執行向下轉型 將向下轉型動作轉移到函式中


    Object lastReading() {
        return readings.lastElement();
    }

to


    Reading lastReading() {
        return (Reading) readings.lastElement();
    }

動機 將一個方法最有效的返回值進行返回給函式的呼叫者 如果型別是準確的,檢查使用這個物件的方法並提供一個更為有效的方法

54. Replace Error Code with Exception (以異常取代錯誤碼)

某個函式返回一個特定的程式碼,用以表示某種錯誤情況 改用異常將其丟擲去


    int withdraw(int amount) {
        if (amount > _balance)
            return -1;
        else {
            _balance -= amount;
            return 0;
        }
    }

to


    void withdraw(int amount) throws BalanceException {
        if (amount > _balance)
            throw new BalanceException();
        _balance -= amount;
    }

動機 當一個程式在發生了一個不可處理的錯誤時,你需要使這個函式的呼叫者知道。向上丟擲異常,讓上層呼叫者知道

55. Replace Exception with Test (已測試取代異常)

面對一個呼叫者可以預先檢查的條件,你丟擲了一個異常 修改呼叫者,使它在呼叫函式之前先做檢查


    double getValueForPeriod (int periodNumber) {
        try {
            return _values[periodNumber];
        } catch (ArrayIndexOutOfBoundsException e) {
            return 0;
        }
    }

to


    double getValueForPeriod (int periodNumber) {
        if (periodNumber >= _values.length)
            return 0;
        return _values[periodNumber];
}

動機

  • 不要過度使用異常,它們應該被用在檢查異常上
  • 不要用它來代替條件測試
  • 檢查異常錯誤條件在呼叫方法之前

11. DEALING WITH GENERALIZATION (處理概括關係)

56. Pull up field (欄位上移)

兩個子類擁有相同的欄位 將該欄位移到超類中


    class Salesman extends Employee{
        String name;
    }
    class Engineer extends Employee{
        String name;
    }

to


    class Employee{
        String name;
    }
    class Salesman extends Employee{}
    class Engineer extends Employee{}

動機

  • 刪除重複的程式碼
  • 允許將子類的行為轉移到父類中

57. Pull Up Method (建構函式本體上移)

你在各個子類中擁有一些建構函式,他們的本體幾乎完全一致 在超類中新建一個建構函式,並在子類建構函式中呼叫它


    class Salesman extends Employee{
        String getName();
    }
    class Engineer extends Employee{
        String getName();
    }

to


    class Employee{
        String getName();
    }
    class Salesman extends Employee{}
    class Engineer extends Employee{}

動機

  • 消除重複的行為

58. Pull Up Constructor Body (建構函式本體上移)

在子類中的建構函式與父類中的建構函式是相同的 在超類中建立一個建構函式,並在子類建構函式中呼叫它


    class Manager extends Employee...
        public Manager (String name, String id, int grade) {
        _name = name;
        _id = id;
        _grade = grade;
    }

to


    public Manager (String name, String id, int grade) {
        super (name, id);
        _grade = grade;
    }

動機

  • 建構函式與方法不同
  • 消除重複的程式碼

59. Push Down Method (方法下移)

父類的某個方法至於某個子類相關 將其移到子類中


    class Employee{
        int getQuota();
    }
    class Salesman extends Employee{}
    class Engineer extends Employee{}

to


    class Salesman extends Employee{
        int getQuota();
    }
    class Engineer extends Employee{}

動機 當方法只在子類中顯現

60. Push Down Field (欄位下移)

超類的欄位只在某個子類中用到 將這個欄位移到需要它的那些子類中去


    class Employee{
        int quota;
    }
    class Salesman extends Employee{}
    class Engineer extends Employee{}

to


    class Salesman extends Employee{
        int quota;
    }
    class Engineer extends Employee{}

動機 當一個欄位只在子類中使用時

61. Extract Subclass (提煉子類)

類中的某些特性只被某些例項用到 新建一個子類,將上面所說的那一部分特性移到子類中去


    class JobItem   {
        getTotalPrices()
        getUnitPrice()
        getEmployee()
    }

to


    JobItem {
        getTotalPrices()
        getUnitPrice()
    }
    class class LabotItem extends JobItem   {
        getUnitPrice()
        getEmployee()
    }

動機 當一個類的行為只用在某些例項中而不用在其他類中

62. Extract Superclass (提煉超類)

兩個類具有相似特性 建立一個父類,然後將這兩個類中相同的部分移到父類中,然後在繼承這個父類


    class Department{
        getTotalAnnualCost()
        getName()
        getHeadCount
    }
    class Employee{
        getAnnualCost()
        getName()
        getId
    }

to


    class Party{
        getAnnualCost()
        getName()
    }
    class Department {
        getAnnualCost()
        getHeadCount
    }
    class Employee {
        getAnnualCost()
        getId
    }

動機 當兩個類有過多相似的地方的時候,就需要考慮下是否需要將這個類進行下抽象了

63. Extract Interface (提煉介面)

若干客戶使用類介面中的同一個子類,或者兩個類的介面有相同的部分 將相同的子集提煉到一個獨立介面中


    class Employee {
        getRate()
        hasSpecialSkill()
        getName()
        getDepartment()
    }

to


    interface Billable  {
        getRate()
        hasSpecialSkill()
    }
    class Employee implements Billable  {
        getRate
        hasSpecialSkill()
        getName()
        getDepartment()
    }

動機

  • 若一個類的子集明確被一系列的客戶使用
  • 如果一個類需要和多個類處理並能處理確定的請求

64. Collapse Hierarchy (摺疊繼承體系)

超類和子類無太大區別 將它們合為一個


    class Employee{ }
    class Salesman extends Employee{    }

to


    class Employee{ }

動機 該子類沒有帶來任何價值

65. Form Template Method (塑造模板函式)

有些子類,其中對應的某些函式以相同順序執行類似的操作,但各個操作的細節上有所不同 將這些操作分別放進獨立函式中,並保持它們都有相同的簽名,於是原函式也就變得相同了。然後將原函式上移到超類


    class Site{}
    class ResidentialSite extends Site{
        getBillableAmount()
    }
    class LifelineSite extends Site{
        getBillableAmount()
    }

to


    class Site{     
        getBillableAmount()
        getBaseAmount()
        getTaxAmount()
    }
    class ResidentialSite extends Site{
        getBaseAmount()
        getTaxAmount()
    }
    class LifelineSite extends Site{
        getBaseAmount()
        getTaxAmount()
    }

動機

66. Replace Inheritance with Delegation (以委託取代繼承)

某個子類只使用了超類介面中的一部分,或是根本不需要繼承而來的資料 _在子類中建立一個欄位用以儲存超類,調整子類函式,令它改2而委託超類:然後去除兩者之間的繼承關係


    class Vector{
        isEmpty()
    }

    class Stack extends Vector {}

to


    class Vector {
        isEmpty()
    }

    class Stack {
        Vector vector
        isEmpty(){
            return vector.isEmpty()
        }
    }

動機

  • 當你使用委託類的時候會對你使用的資料欄位更加的清晰
  • 在你控制之外的方法都會被忽略,你只需要關注於自身

67. Replace Delegation with Inheritance (以繼承取代委託)

你在兩個類之間使用簡單的委託關係,並經常為整個介面編寫許多很多簡單的委託函式 讓委託類繼承委託類


    class Person {
        getName()
    }

    class Employee {
        Person person
        getName(){
            return person.getName()
        }
    }

to


    class Person{
        getName()
    }
    class Employee extends Person{}

動機

  • 如果你使用所有的方法在委託類中
  • 如果你不能使用委託類的所有函式,那麼久不應該使用它

12. BIG REFACTORINGS (大型重構)

68. Tease Apart Inheritance (梳理並分解繼承體系)

某個繼承體系同時承擔兩項責任 建立兩個繼承體系,並通過委託關係讓其中一個可以呼叫另外一個


    class Deal{}
    class ActiveDeal extends Deal{}
    class PassiveDeal extends Deal{}
    class TabularActiveDeal extends ActiveDeal{}
    class TabularPassiveDeal extends PassiveDeal{}

to


    class Deal{
        PresentationStyle presettationStyle;
    }
    class ActiveDeal extends Deal{}
    class PassiveDeal extends Deal{}

    class PresentationStyle{}
    class TabularPresentationStyle extends PresentationStyle{}
    class SinglePresentationSt