Search code examples
javacode-duplication

Avoid duplicated code if the only diffence is a method call in a for loop


I have this two methods written in java:

public void fillRect(float x, float y, float width, float height, Color color) {
        int xi = mapX(x);
        int yi = mapY(y);

        int heightf =  mapHeight(height);
        int widthf  = mapWidth(width);


        if (xi + widthf > pixelWidth){
            widthf -= xi + widthf - pixelWidth;
        }
        if (yi + heightf > pixelHeight){
            heightf -= yi + heightf - pixelHeight;
        }


        if (xi < 0) {
            widthf += xi;
            xi = 0;

        }
        if (yi < 0) {
            heightf += yi;
            yi = 0;
        }

        for (int xx = xi; xx < xi + widthf; xx++){
            for (int yy = yi; yy < yi + heightf; yy++){
                // here is the difference between the other method
                setPixel(xx,yy,color);

            }
        }
    }
public void fillRect(float x, float y, float width, float height,float transparency, Color color) {
        int xi = mapX(x);
        int yi = mapY(y);

        int heightf =  mapHeight(height);
        int widthf  = mapWidth(width);

        if (xi + widthf > pixelWidth){
            widthf -= xi + widthf - pixelWidth;
        }
        if (yi + heightf > pixelHeight){
            heightf -= yi + heightf - pixelHeight;
        }


        if (xi < 0) {
            widthf += xi;
            xi = 0;

        }
        if (yi < 0) {
            heightf += yi;
            yi = 0;
        }

        for (int xx = xi; xx < xi + widthf; xx++){
            for (int yy = yi; yy < yi + heightf; yy++) {
                // here is the difference between the other method
                // this Method is slower then setPixel() 
                plot(xx,yy,transparency,color);
            }
        }
    }

I'm used to write a method like this validateBoundary(float* x,float* y, float* width, float *height): void which includes the 'if-statments' and call it instead but clearly this won't be happen in Java. What is the solution for problems like this? We could write a Methode validateBoundaryWidthf(xi, widhtf, pixelWitdth) which returns the the new value for widthf. But something like this:

if (xi < 0) {
     widthf += xi;
     xi = 0;
}

can't be a solved by this because there is only one return value. Sure I could create a POJO with the attributes widthf and xi an return this instead but I assume this is comes expensive in terms of cpu / memory. So what is the proper way solving this duplicated code issue?


Solution

  • You can use consumers to handle the different handling inside the for loop. Define a new functional interface which takes the xx and yy values as arguments:

    @FunctionalInterface
    public interface PointConsumer {
        void accept(int x, int y);
    }
    

    Then you add a new method performOnPoints with all the arguments needed and with one PointConsumer argument. It might look like this:

    public void performOnPoints(float x, float y, float width,
                                float height, PointConsumer consumer) {
        int xi = mapX(x);
        int yi = mapY(y);
    
        int heightf =  mapHeight(height);
        int widthf  = mapWidth(width);
    
    
        if (xi + widthf > pixelWidth){
            widthf -= xi + widthf - pixelWidth;
        }
        if (yi + heightf > pixelHeight){
            heightf -= yi + heightf - pixelHeight;
        }
    
    
        if (xi < 0) {
            widthf += xi;
            xi = 0;
    
        }
        if (yi < 0) {
            heightf += yi;
            yi = 0;
        }
    
        for (int xx = xi; xx < xi + widthf; xx++){
            for (int yy = yi; yy < yi + heightf; yy++){
                consumer.accept(xx, yy);
            }
        }
    }
    

    Then you can rewrite your existing fillRect methods like this:

    public void fillRect(float x, float y, float width, float height, Color color) {
        performOnPoints(x, y, width, height, (xx, yy) -> setPixel(xx, yy, color));
    }
    
    public void fillRect(float x, float y, float width, float height,
            float transparency, Color color) {
        performOnPoints(x, y, width, height, (xx, yy) -> plot(xx,yy,transparency,color);
    }
    

    As you see they both use the same looping code with all the special if() statements, but you have this code only once. For the different arguments you will use a different consumer object, one will call setPixel(), the other will call plot().