mardi 26 octobre 2021

Design pattern for decoupling core and auxiliary functionality

Consider a synchronous (Java) function WORKER which takes some input and returns some output. I will refer to this as the core functionality. Its exact nature and implementation is irrelevant; what's important, however is that function WORKER is huge, it consists of multiple steps/substeps/subsubsteps and it puts in motion a large OOP machinery while calculating the output.

There are some auxiliary features, which are orthogonal to the core functionality: WORKER has to be monitored/tracked and reported (statistics at the end). Currently the auxiliary functionalities are embedded in the core functionality resulting in a hard-to-maintain, cumbersome code.

The question is this: are there some design patterns to decouple the core and auxiliary functionalities?

My first idea was to define WORKER with the help of a set of possible states. Reaching any of those states would trigger the callback of some registered observers (through which the auxiliary functionality is solved.) Here is the code demonstrating the idea:

public class StateMachine<State> {
    protected State state;
    public StateMachine(State state) {this.state = state;}
    public ArrayList<StateObserver<State>> observers = new ArrayList<>();
    public void transit(State newState) {
        observers.stream()
            .filter(o -> o.from.equals(state) && o.to.equals(newState))
            .forEach(o -> o.callback.run());
        state = newState;
    }
    public void registerObserver(State from, State to, Runnable callback) {
        observers.add(new StateObserver<State>(from, to, callback));
    }
}
@AllArgsConstructor public class StateObserver<State> {
    State from, to;
    Runnable callback;
}
@Getter public class Worker extends StateMachine<Worker.State> {
    enum State { INIT, WORKING, DONE; } 
    public Worker() { super(State.INIT); }
    @SneakyThrows public void doWork() {
        for (int i = 0; i < 10; ++i) {
            transit(State.WORKING);
            Thread.sleep(100);
        }
        transit(State.DONE);
    }
}
public class Progress {
    public Progress(Worker worker) { 
        worker.registerObserver(Worker.State.INIT, Worker.State.WORKING, () -> System.out.print("Starting...\n[ "));
        worker.registerObserver(Worker.State.WORKING, Worker.State.WORKING, () -> System.out.print("\b=>"));
        worker.registerObserver(Worker.State.WORKING, Worker.State.DONE, () -> System.out.print("\b]\ndone.\n"));
    }
}
public class App {
    public static void main(String[] args){
        Worker worker = new Worker();
        new Progress(worker);
        worker.doWork();
    }
}

This code, however, has some serious limitations:

  1. it's not clear what's the best way to create sub-states for Worker (sub-Workers?)
  2. how sub-substates should be advertised for observers for registration
  3. how the observers access arbitrary intermediate/temporary objects from Worker (e.g. the loop variable i in the above example)

Aucun commentaire:

Enregistrer un commentaire