Core Java

How to change procedural code into object-oriented one?

What style should Clean Code be written in?

Clean Code is not always object-oriented. Sometimes it will be written in procedural style. And what style is better: procedural or object-oriented? We should perform the choice under given conditions which facilitates its development and readability – in accordance with the principles of Clean Code.

Below is an example of the procedural code that will help me consider the purity of the code and its refactoring to the object oriented code.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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");
    }
}

I choose the style in which the code will be written on the basis of observing the direction of changes that result from emerging new business requirements.

What changes does the procedural code allow?

If I mainly add new functions operating on already existing data structures, then the procedural code (new procedures) will probably remain legible. An example is the new function that returns the smallest rectangle containing given figure.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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");
    }
}

When will the procedural code become illegible?

But if you plan to add or modify existing data structures, it will force changes to all existing procedures. What happens when I decide to change the components in the Rectangle data structure to points describing 2 opposite corners of the square?

1
2
3
4
5
6
7
8
public class Point {
    double x,y;
}
 
public class Rectangle {
     Point topLeft;
     Point bottomRight;
}

It is not difficult to notice that such a change will force many changes to existing procedures. A way to avoid many changes (or minimize them) is to place the getX () and getY () methods in the Rectangle structure that will perform the necessary calculations.

01
02
03
04
05
06
07
08
09
10
11
12
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);
    }
}

But note that from that moment I start to hide details of the data structure. Details in the Rectangle class have been hidden and new methods calculate the necessary output. In this way, I am starting to change the code style from procedural to object oriented.

How to refactor a procedural code into an object-oriented one?

Perform self-encapsulation of data structures

At the beginning I add constructors and encapsulate details within data structures. In my case, the data in the structures are not changing, so the fields can be final.

01
02
03
04
05
06
07
08
09
10
11
public class Circle {
    private final double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    public double getRadius() {
        return radius;
    }
}

Define a common interface / base class for existing data structures

Next, I define an empty “Shape” base class that will expand all data structures. From now on, the “area” procedure accepts only the “Shape” abstract class extension as a parameter. Alternatively, it can also be a common interface.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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;
    }
}
 
...

Move the logic from the procedure to the base class

In order to transfer the logic to the base class, I will make a small modification to be able to use the method transfer in the IntelliJ tool.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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());
    }
}

I obtained the above code by extracting a new method “calculateArea”, then deleting the word static and adding a call to the constructor.

Then I move the method containing the “calculateArea” logic from “Geometry” to the “Shape” base class.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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());
    }
}

After this distortion, there was a code smell: “base class is dependent on its derived classes”. Solving the problem will lead us to the next transformation.

Push method down

The transformation is fully automated in many environments like IntelliJ, Eclipse, NetBeans.

Delete unnecessary logic in derived classes

Finally, we finish with the transformation “replace conditional expressions with polymorphism”. In each of the subclasses (i.e. our old data structures), only one condition will be true.

The final result of our refactoring is below

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Circle extends Shape {
    private final double radius;
 
    public Circle(double radius) {
        this.radius = radius;
    }
 
    public double getRadius() {
        return radius;
    }
 
    @Override
    double calculateArea() {
        Circle circle = (Circle) this;
        return Math.PI * circle.getRadius() * circle.getRadius();
    }
}
 
public class Geometry {
    static double area(Shape shape) {
        return shape.calculateArea();
    }
}

In addition, we can inline “Geometry.area” function and then change the name of “calculateArea” to “area”, so we come back to old naming.

I also recommend my article about refactoring to the Interpreter pattern. Share your impressions from the article below.

Published on Java Code Geeks with permission by Wlodek Krakowski, partner at our JCG program. See the original article here: How to change procedural code into object-oriented one?

Opinions expressed by Java Code Geeks contributors are their own.

Wlodek Krakowski

Włodek Krakowski is an independent technical trainer specializing in maintaining code quality through refactoring. His main interest is taking care of delivering valued software from different perspectives. He is a founder and executive editor of blog at www.refactoring.pl".
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Mamta Sharma
3 years ago

Backend Developer Roadmap for 2020

Backend Developer Roadmap for 2020. In the era of internet-based mobile and web applications, the high reliance on powerful cloud servers and cheaper handheld or portable computing devices have increased the mankind’s accessibility over powerful applications at a cheaper cost. On the other end, dependency on backend computing tasks has grown exponentially that created an ocean of opportunities for backend developers.

Back to top button