10

I have base two classes, Operation and Trigger. Each has a number of subclasses which specialise in certain types of operations or triggers. A Trigger can trigger a specific Operation. Whilst an Operation can be triggered by a specific Trigger.

I need to write the code that maps a given Operation to a given Trigger (or vice versa), but I'm not sure where to put it.

In this case the code doesn't clearly belong to one class or the other class. So in terms of a single-responsibility principle I'm not sure where the code should belong.

I can see three options which would all work. Whilst 1 & 2 are appear to just be a choice of semantics, 3 represents a different approach entirely.

  1. On the trigger, e.g. bool Triggers(Operation o).
  2. On the operation, e.g. bool TriggeredBy(Trigger t).
  3. In an entirely new class which manages the mapping, e.g. bool MappingExists(Trigger t, Operation o).

How should I decide where to place the shared mapping code in respect of a single responsibility principle?

How to manage single responsibility when the responsibility is shared?


Edit 1.

So the actual code looks like this. All the properties, are either a string, Guid, collection<string>, or enum. They are basically just represent small pieces of data.

enter image description here

Edit 2.

The reason for the return type of bool. Another class is going to consume a collection of Trigger and a collection of Operation. It needs to know where a mapping exists between a Trigger, and an Operation. It will use that information to create a report.

  • Why the bool type? – Tulains Córdova Feb 16 '16 at 20:07
  • @user61852 to return a result to the calling code – James Wood Feb 16 '16 at 20:15
  • 1
    What does the calling code do with the boolean? Depending of what you answer to this question I might have the solution. – Tulains Córdova Feb 16 '16 at 20:28
  • @user61852, please see my edits. – James Wood Feb 16 '16 at 20:37
  • 1
    So it has nothing to do with actually executing the trigger of the operation? – Tulains Córdova Feb 16 '16 at 20:46
4

I would think about it this way: how is it determined which Operation causes which Trigger to be triggered. It has to be an algorithm which can change over time, or evolve into multiple algorithms. Putting it in either Trigger or Operation classes imply that those classes will be able to handle such scenarios in the future. Note, that I do not see it as simple as a mapping since there may be more to it.

My choice would be to create a class with appropriate methods, such as GetOperationForTrigger(Trigger t). This allows the code to evolve into a set of such classes the choice of which can depend at runtime or other variables (e.g. strategy pattern).

Note that the main assumption in this line of thinking is to write minimal code (i.e. three classes today) but to avoid major refactoring if the functionality needs to be extended in the future by not making the assumption that there will always be exactly one way to determine which Trigger causes which Operation.

Hope this helps. Although the response is similar to user61852, the reasoning is different. As a result, the implementation will differ (i.e. have explicit methods instead of overriding equals, so the number of methods can evolve over time based on the needs).

5

Been there, done that.

Option #3.

I don't know what language you will be using but I will use a pseudo-code that is very simillar to Java. If your language is C# you probably have similar interfaces and structures.

Have a Mapping class or interface:

public interface Mapping {
    public void setObject1(Object o);
    public void setObject2(Object o);
    public Object getObjecto1();
    public Object getObjecto2();
}
  • Override the equals() method of Mapping so collections of Mapping can be inquired whether they contain a given mapping.
  • The especialized objects should have adecuate equals() methods as well.
  • Also implement the interface Comparable, so you can sort reports.

Them you can simply put a mappings into a collection

List<Mapping> list = new ArrayList<Mapping>();
Hat hat = new Hat();
Bag bag = new Bag();
list.add(new Mapping(hat,bag));

Later you can ask:

// let's say you have a variable named x which is of type Mapping

if ( list.contains(x) ){
    // do some thing
}
0
  1. Break your code down into smaller bits.

Currently you have class A knowing about class B and class B knowing about class A. Thats a lot of coupling going on.

By definition A is doing at least its own operation AND checking if B should be run. The reverse is true with B. Whichever class first called it should be able to look at the result and see if it further things need to be run.

Try and break that coupling by splitting your classes into smaller components. I like to put a comment at the top of each class explaining in plain english what it does. If you need to use words like AND or it goes over a sentence or two then you need to consider breaking it down. As a general rule anything after an "and" should be in its own class

Also see if you can define an interface that covers the functionality of Trigger and Operation. If you cant that thats another indication that your class is getting too large. It will also break the coupling between you classes.

  • 1
    In all honesty I'm not sure I can break my code down further. I've updated my question with the class signatures so you can see, but basically they are pretty lightweight data objects storing a few properties each. In terms of the coupling, yes that is slightly problematic, as effectivly a Trigger would be coupled to a Operation. But that's sort of what the real world data looks like. They are coupled, because there is a mapping, e.g. they have to know about each other to have meaning. – James Wood Feb 16 '16 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.