0

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:

  1. 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.
  2. 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 call methodx 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?

user1803551
  • 262
  • 3
  • 12
  • There's absolutely a much better design than this, but it would help if we knew what these abstract things are. Can you insert more domain-specific type mames so that we know e.g. whether the methods correspond to `rip()`, `mix()`, `burn()` or rather to `filterByName()`, `filterByAge()`, `filterBySize()`? That would make it much easier to see which element has to go where. – Kilian Foth May 11 '16 at 13:24
  • So list1 items should always use method1, list2 items should always use method2, and so on..? Am I understanding that correctly? – Jason Tyler May 11 '16 at 13:27
  • @KilianFoth I added more details. Please see if I can further clarify anything for you. – user1803551 May 11 '16 at 14:16
  • @JasonTyler Correct, though the same item can be in more than one list. See my edited post. – user1803551 May 11 '16 at 14:17
  • The straightforward solution when you have repeated names (i.e. xyz1, xyz2, xyz3), is to design a first-class entity around the individual that is being repeated. In other words, you should work on making a first-class concept (e.g an interface) out of the notion of a single, individual trigger; this will address your naming issues. Then when you have that, create manager concept that is a collection or an assembly of those individual triggers; this will address your pairing problems. Is there a reason this won't work here? – Erik Eidt May 11 '16 at 17:36
  • @ErikEidt I'm not sure what you're suggesting. Can you give a concrete example? – user1803551 May 11 '16 at 18:40
  • Sometimes a collection of individuals is better than an individual that has multiple similar features. Have a look here, where a programmer was doing [something similar with relays](http://programmers.stackexchange.com/q/317724/63202) – Erik Eidt May 11 '16 at 21:23
  • @ErikEidt I don't think the relays situation is the same, because the relays are completely separated. My "triggers" can register to any number of events, and they can also initiate events. I think it'll be best if you can write 1 example of how you think it should work based on the details I posted. Thank you. – user1803551 May 12 '16 at 15:03
  • Ok,I don't understand how your triggers need to work, but I can tell you that you should not replicate methods of the same signature differentiated only by a number. Make an interface that concerns itself with a single trigger. Then group triggers into a larger construct using a different class. You can use more than one level of composition if required. – Erik Eidt May 13 '16 at 00:02
  • @KilianFoth Any ideas after my update? – user1803551 May 14 '16 at 02:14
  • @ErikEidt The name of the method doesn't matter, call them `john, maddy, rick` etc. if that's any difference. I can make an interface which will contain that method and implement it in each Trigger that needs it. Then what? I still need to keep a list for each trigger over which I can iterate. Again, if you can write a concrete example for 1 trigger which registers to 2 events like I did, it would solve a lot of problems. – user1803551 May 14 '16 at 02:27
  • I asked if the signatures were the same and thus, the methods only differ by name; sorry for the confusion. – Erik Eidt May 14 '16 at 04:56
  • @ErikEidt The name is part of the signature. If the names aren't the same, the signatures can't be the same. If you are asking about the arguments, then most of them are void (as in my example). but some are not. Not sure why that would matter but if it helps solving this then here it is. – user1803551 May 14 '16 at 09:46

0 Answers0