Search code examples
javacode-duplicationredundancyanti-patterns

Reducing Java code-duplication when iteratively applying an operation on a list until stable


I am attempting to reduce code duplication in my java code. I am not a java expert, so I might be missing something basic. The goal is to operate on a list via several operators, foo1, foo2, ..., fooN.

Each operation is iterated until the list stabilizes (no longer changes by the operation). Each operation consists of a forward pass, and then a backwards pass. The passes are the same, just the list is reversed. Each pass is also iterated until the list stabilizes. It's important that the operators are applied sequentially, resulting in frustrating code duplication.

Below is some pseudo-java showing how it is currently done. Note: the foo operations here attempt to modify list arg1; they return true if (and only if) a modification to arg1 occurred.

reduceByFoo1(arg1,arg2,arg3){
        doUpdate = true;
        while(doUpdate) {
            doUpdate = false;

            doPass = true;
            while(doPass) {
                doPass = foo1(arg1,arg2,arg3);
                doUpdate |= doPass;
            }
            Collections.reverse(arg1);

            doPass = true;
            while(doPass) {
                doPass = foo1(arg1,arg2,arg3);
                doUpdate |= doPass;
            }
            Collections.reverse(arg1);
        }
    }

reduceByFoo2(arg1,arg2,arg3){
...same code as above, but with foo2..
}

reduceByFoo2(arg1,arg2,arg3){
...same code as above, but with foo3..
}

...and so on...

What might be a good way to reduce duplication or improve the design pattern?

EDIT: Using the accepted answer, this is roughly the code I ended up with. Much better now:

interface Foo {boolean bar(arg1, arg2, arg3);}

fooUntilStable(foo, arg1, arg2, arg3) = {
    iterate = true;
    updated = false;
    while(iterate) {
        iterate = foo.bar(arg1, arg2, arg3);
        updated |= iterate;
    }
    return updated;
}

biFooUntilStable(foo, arg1, arg2, arg3){
    Foo biFoo = (a1, a2, a3) -> {
             updated = fooUntilStable(foo, a1, a2, a3);
             Collections.reverse(a1);
             updated |= fooUntilStable(foo, a1, a2, a3);
             Collections.reverse(a1);
             return updated;
    }
    fooUntilStable(biFoo,arg1,arg2,arg3);
}

Foo foo1 = (arg1,arg2,arg3) -> {...}
Foo foo2 = (arg1,arg2,arg3) -> {...}
...
Foo fooN = (arg1,arg2,arg3) -> {...}

biFooUntilStable(foo1,arg1,arg2,arg3);
biFooUntilStable(foo2,arg1,arg2,arg3);
...
biFooUntilStable(fooN,arg1,arg2,arg3);


Solution

  • The way I handle functions that do the same thing, but slightly different, is like this:

    reduceByFoo1(arg1,arg2,arg3){
       // do common operation
       foo1(arg1, arg2, arg3);
       // do stuff
    }
    
    
    reduceByFoo2(arg1,arg2,arg3){
       // do common operation
       foo2(arg1, arg2, arg3);
       // do stuff
    }
    

    In other programming languages, we can pass functions as arguments, but Java is slightly different. Check out this post for passing methods as parameters.

    But what I would do is this:

    public void reduce(arg1, arg2, arg3, someFunc) {
       // do common operation
       someFunc(arg1, arg2, arg3);
       // do common operation
    }
    

    based on the info from the mentioned SO post.

    The, you can just call reduce with the specified function like:

    reduceByFoo1(arg1,arg2,arg3){
       // do common operation
       reduce(arg1, arg2, arg3, foo1);
       // do stuff
    }
    
    
    reduceByFoo2(arg1,arg2,arg3){
       // do common operation
       reduce(arg1, arg2, arg3, foo2);
       // do stuff
    }
    

    Edited to add: This other post for other ideas that may fit a little better into your request.