如何將過程程式碼變成物件導向的程式碼? - WLODEK

banq發表於2020-07-17

乾淨clean程式碼並不總是物件導向的。有時它將以程式樣式編寫。哪種風格更好:過程式還是物件導向?我們應該在一定條件下進行選擇,以使其易於開發和可讀,根據“Clean守則”的原則。
下面是過程程式碼的示例,它將幫助我考慮程式碼的純度及其對物件導向程式碼的重構。

public class Rectangle {
    double width;
    double height;
}
...
public class Geometry {
    double area(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.radius * circle.radius
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.width * rectangle.height;
        } else if (shape instanceof Square) {
            Square square = (Square) shape;
            return square.size * square.size;
        }
 
        throw new IllegalArgumentException("Unknown shape");
    }
}


如果我主要新增對已經存在的資料結構進行操作的新函式,那麼上述程式碼仍然還是清晰易讀。新函式是返回包含給定圖形的最小矩形。

public class Geometry {
    Rectange containingRectange(Object shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            Rectangle rectangle = new Rectangle();
            rectangle.width = 2 * circle.radius;
            rectangle.height= 2 * circle.radius;
            return rectangle;
        } else if (shape instanceof Rectangle) {
            return (Rectangle) shape;
        } else if (shape instanceof Square) {
            ...
        }
 
        throw new IllegalArgumentException("Unknown shape");
    }
}


但是,如果您計劃新增或修改現有資料結構,則將強制更改所有現有過程性程式碼。當我決定將Rectangle資料結構中的元件更改為描述正方形2個相對角的點時會發生什麼?

public class Point {
    double x,y; 
} 
 
public class Rectangle {
     Point topLeft;
     Point bottomRight; 
}

不難看出,這樣的改變將迫使對現有程式進行許多改變。避免許多更改(或將其最小化)的一種方法是將getX()和getY()方法放置在Rectangle結構中,該結構將執行必要的計算。

public class Rectangle {
    private Point topLeft;
    private Point bottomRight;
 
    double getX(){
        return Math.abs(topLeft.x = bottomRight.x);
    }
 
    double getY(){
        return Math.abs(topLeft.y = bottomRight.y);
    }
}

但是請注意,從那一刻起,我開始隱藏資料結構的細節。Rectangle類中的詳細資訊已隱藏,新方法將計算必要的輸出。透過這種方式,我開始將程式碼樣式從過程更改為物件導向。

如何將過程程式碼重構為物件導向的程式碼?

1. 
執行資料結構的自封裝,首先,我新增了建構函式並將細節封裝在資料結構中。就我而言,結構中的資料沒有改變,因此這些欄位可以是最終的。

public class Circle {
    private final double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    public double getRadius() {
        return radius;
    }
}


2.為現有資料結構定義一個公共介面/基類,接下來,我定義一個空的“ Shape”基類,它將擴充套件所有資料結構。從現在開始,“area區域”計算過程僅接受“Shape”抽象類擴充套件作為引數。或者,它也可以是一個通用介面。

public abstract class Shape{
}
 
public class Circle extends Shape {
    private final double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    public double getRadius() {
        return radius;
    }
}


3.將邏輯從過程程式碼移至基類中,為了將邏輯傳遞給基類,我將做一些小的修改以能夠使用IntelliJ工具中的方法傳遞。

public class Geometry {
    static double area(Shape shape) {
        return new Geometry().calculateArea(shape);
    }
 
    private double calculateArea(Shape shape) {
        if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        } else if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        } else if (shape instanceof Square) {
            Square square = (Square) shape;
            return square.getSize() * square.getSize();
        }
 
        throw new IllegalArgumentException("Unknown shape :" + shape.getClass());
    }
}


透過提取一個新方法“ calculateArea”:計算區域的邏輯。
然後,將包含“ calculateArea”邏輯的方法從“ Geometry”移到“ Shape”基類。

public class Geometry {
    static double area(Shape shape) {
        return shape.calculateArea();
    }
}
 
public abstract class Shape {
    double calculateArea() {
        if (this instanceof Circle) {
            Circle circle = (Circle) this;
            return Math.PI * circle.getRadius() * circle.getRadius();
        } else if (this instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) this;
            return rectangle.getWidth() * rectangle.getHeight();
        } else if (this instanceof Square) {
            Square square = (Square) this;
            return square.getSize() * square.getSize();
        }
 
        throw new IllegalArgumentException("Unknown shape :" + getClass());
    }
}


這裡有一種程式碼壞味道:“基類取決於其派生類”。解決問題將使我們進入下一個轉變。使用下推方法,在IDE中選擇重構中的Push Members Down。

4.刪除派生類中不必要的邏輯,最後,我們完成了“用多型替換條件表示式”的轉換。在每個子類(即我們的舊資料結構)中,只有一個條件為真。

如何將過程程式碼變成物件導向的程式碼? - WLODEK
重構後的結果程式碼:

public class Circle extends Shape {
    private final double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    @Override
    double calculateArea() {
        return Math.PI * circle.radius * circle.radius;
    }
}
 
public class Geometry {
    static double area(Shape shape) {
        return shape.calculateArea();
    }
}

另外,我們可以內聯“ Geometry.area”函式,然後將“ calculateArea”的名稱更改為“ area”,因此我們回到原來的命名。

 

相關文章