I am implementing a trigger system. There are objects that take the role of observers (this is not strictly an observer pattern because the observers are inter-dependent, see my question here). They register to be triggered during certain events (by being added to the list of that event) and provide the functionality when such an event happens (by implementing the appropriate method).
I have the following interface:
public interface Triggerable {
public boolean triggerOnEvent1();
public boolean triggerOnEvent2();
public boolean triggerOnEvent3();
// etc.
}
and "corresponding" lists bundled in this class:
public class TriggerManager {
static List<Triggerable> objectsToTriggerOnEvent1;
static List<Triggerable> objectsToTriggerOnEvent2;
static List<Triggerable> objectsToTriggerOnEvent3;
// etc.
// Getters for the lists: getObjectsToTriggerOnEventx()
}
I am using an adapter pattern here, which I think is irrelevant to the question, but it makes the code easier to read:
public abstract class AbstractTrigger implements Triggerable {
@Override
public boolean triggerOnEvent1() { return false; }
@Override
public boolean triggerOnEvent2() { return false; }
@Override
public boolean triggerOnEvent3() { return false; }
// etc.
}
This is an example of a "trigger":
public class TriggerExample extends AbstractTrigger {
public TriggerExample () {
// register to specific events
TriggerManager.getObjectsToTriggerOnEvent2().add(this);
TriggerManager.getObjectsToTriggerOnEvent6().add(this);
}
@Override
public boolean triggerOnEvent2() {
// do something
return true;
}
@Override
public boolean triggerOnEvent6() {
// do something else
return true;
}
}
When an event happens somewhere, it is notified to all registered "observers" using this iteration:
for (int i = 0; i < TriggerManager.getObjectsToTriggerOnEventx().size(); i++) { // x stands for any of the above numbers
Triggerable triggerable = TriggerManager.getObjectsToTriggerOnEventx().get(i);
if (triggerable.triggerOnEventx()) {
// do something
}
}
The implementations of triggerOnEventx
vary greatly and themselves can cause triggers with the loop construct above.
My concerns are twofold:
- I need to maintain the same data twice - for every method I add/remove from the interface (and the adapter), I need to add/remove a list. Reduces maintainability.
- I need to be careful not to use the wrong combination of a list and a method in the iteration - iterating on
listx
will always callmethodx
on its contents. Bug prone.
One thing I thought of doing is creating a Map<List, Method>
in order to solve point 2. The thing is that it doesn't solve 1 and that it uses reflection, which comes with its own problems (like getting the method by name, which is also bug prone).
I can modify both of the types above and create new things, so the solution doesn't have to be limited to the above structure. What is a good design that suits this?