干净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类中的详细信息已隐藏,新方法将计算必要的输出。通过这种方式,我开始将代码样式从过程更改为面向对象。如何将过程代码重构为面向对象的代码?
执行数据结构的自封装,首先,我添加了构造函数并将细节封装在数据结构中。就我而言,结构中的数据没有改变,因此这些字段可以是最终的。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”,因此我们回到原来的命名。