Search code examples
javarefactoringcode-duplication

Java refactoring code duplication


I have code that repeats a lot, but I don't know how to refactor it properly.

I have a class Foo, it parses the network messages/data it receives from a socket and calls the corresponding onEvent() methods. Foo is purely a parser of network messages, it has no logic as to what action to take for the events it receives. Whoever wants to add such logic must subclass Foo and override the onEvent() methods.

abstract class Foo {
    void processNetwotkMessage(String message) {
        ...
        onEvent1(arg1, arg2, arg3)
        reutn;
        ...
        onEvent2(arg4);
        return;
        ...
        onEvent3()
        return;
        ...
        onEvent999(arg1337);
    }

    abstract protected void onEvent1(Arg1 arg1, Arg2 arg2, Arg3 arg3);
    abstract protected void onEvent2(Arg4 arg4);
    abstract protected void onEvent3();
    ...
    abstract protected void onEvent999(Arg1337 arg1337);
}

Now, my program is supposed to be modular, I have many separate module classes that want to receive those events and process them. The modules implement Plugin interface. The interface matches the onEvent() methods from Foo, but adds PluginContext ctx as the first argument.

interface Plugin {
    void onEvent1(PluginContext ctx, Arg1 arg1, Arg2 arg2, Arg3 arg3);
    void onEvent2(PluginContext ctx, Arg4 arg4);
    void onEvent3(PluginContext ctx);
    ...
    void onEvent999(PluginContext ctx, Arg1337 arg1337);
}

And now, to dispatch the events to the modules, I creating a module-aware subclass of Foo called PluginSupporingFoo.

class PluginSupporingFoo extends Foo implements PluginContext {
    List<Plugin> plugins;

    @Override
    protected void onEvent1(Arg1 arg1, Arg2 arg2, Arg3 arg3) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent1(this, arg1, arg2, arg3);
            }
        }
    }

    @Override
    protected void onEvent2(Arg4 arg4) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                if (PluginIsAllowedToBeAwareOfThisEvent(p, arg4)) {
                    p.onEvent2(this, arg4);
                }
            }
        }
    }

    @Override
    protected void onEvent3() {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent3(this);
            }
        }
    }

    ...

    @Override
    protected void onEvent999(Arg1337 arg1337) {
        synchronized (plugins) {
            for (Plugin p : plugins) {
                p.onEvent999(this, arg1337);
            }
        }
    }
}

As you can see, whenever Foo calls one of onEvent() methods, the overrided method from PluginSupporingFoo gets called and then it dispatches this event to all of modules by calling the corresponding onEvent() method of Plugin interface, with one extra argument added -- PluginContext ctx. Sometimes there is also a condition whether to tell a module about an event or not, like you can see in PluginSupporingFoo.onEvent2().

Now, there is a lot of code duplication going on that I would like to remove.

  1. First off, Plugin interface and Foo class have almost identical methods. In fact, Plugin interface needs to have all onEvent methods Foo has but with PluginContext ctx as the an extra first argument.

  2. Another code duplication is in PluginSupporingFoo. All onEvent() methods are pretty-much a copy of each other:

.

protected void on${EventName} ( ${ArgList} ) {
    synchronized (plugins) {
        for (Plugin p : plugins) {
            ${ OPTIONAL: if (filter(p, ${ArgList}.arg1)) { }
                p.on{EventName}(this, ${ArgList}.allArgs);
            ${ OPTIONAL: } }
        }
    }
}

And given that there are many many onEvent methods, it's frustrating to have so much copy-paste code and it would be hard to modify them all if needed.


Solution

  • Wow... this is a poor design to have eventXX( foo, bar, baz) etc because each time you add a new event you must add a corresponding listener method.

    Perhaps a better design would be to refactor this so that your Foo class only has a few or ideally one onEvent() method that takes a new Event interface

    public class Foo{
    
      void onEvent(Event e){ ... }
    
    } 
    
    public interface Event{
         Object[] getArgs();
    
         //other Event specific methods
         ...
    }
    

    Then each of the eventXX methods would be a new implementation of the Event interface.

    public class Event2 implements Event{
    
       public Object[] getArgs(){
           //Arg4 like in your code
           return new Object[]{ new Arg4() };
       }
    }
    

    the Plugin could could would also similarly only have 1 method

    interface Plugin{
    
       onEvent(PluginContext ctx, Event e);
    
    }
    

    Now whenever you need to add a new Event it's just a new Event implementation and these interfaces don't need any extra methods.

    Handlers can check the type of Event or you can make EventType or other kind of discriminator however you wish.

     class MyPlugin implements Plugin{
         public void onEvent(PluginContext ctx, Event e){
             //this is only useful if we only care about a few types
             if( e instanceOf Event2){
                //we know this is Arg4
                Arg4 arg4 = (Arg4) e.getArgs()[0];
                ...
             }
    
         }
     }
    

    Now with Java Lambdas we we could even have a handler Map<Class<? extends Event>, Function> if you wanted to get fancy.