如何将过程代码变成面向对象的代码? - WLODEK


干净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.删除派生类中不必要的逻辑,最后,我们完成了“用多态替换条件表达式”的转换。在每个子类(即我们的旧数据结构)中,只有一个条件为真。


重构后的结果代码:

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”,因此我们回到原来的命名。