0

I am working on some generalization of my code and I'm struggling with some cases which are irritating me because I think I'm having code duplicity.

1) I'm often working with some map<string, set<string>> or map<string, set<SObject>>. Therefore, I wanted to generalize the following behaviour, which either creates the key in the map if it doesn't exist or update the set if the key exists. Whether my set is a string or SObject I have the same code, replacing SObject by string. Therefore, I feel there is duplicity of code and I don't like it. Any idea how to solve that ? My 2 methods:

public static map<string, set<string>> put_or_update_map(map<string, set<string>> original_map, string key, string value){
    if(original_map.containskey(key)){
        original_map.get(key).add(value);
    }
    else{
        original_map.put(key, new set<string>{value});
    }
    return original_map;
}


public static map<string, set<SObject>> put_or_update_map(map<string, set<SObject>> original_map, string key, SObject value){
    if(original_map.containskey(key)){
        original_map.get(key).add(value);
    }
    else{
        original_map.put(key, new set<SObject>{value});
    }
    return original_map;
}

2) With the SObject method above which is generalized for all SObject, I'm having troubles to call it.

For instance, I have a main method which does many stuffs and requires a map to be of <string, set<Opportunity>> Therefore, my main method can be summarized like this, with the way I'd call the method described above as follows:

map<string, set<Opportunity>> my_map = map<string, set<Opportunity>>();

(.... create and do some stuff with my_map, requiring it to be of <string, set<Opportunity> ...)


    for(Opportunity my_opp: all_opps){
        attempt1: my_map = put_or_update_map(my_map, my_opp.field, my_opp); //error: invalid signature
        attempt2: my_map = put_or_update_map(my_map, my_opp.field, (SObject) my_opp); //error: invalid signature
    }


(.... do some other stuff with my_map, requiring it to be of <string, set<Opportunity> ...)

Therefore, how can I do to properly call this method generalized every time I need it ? Duplicating it for all my SObject doesn't have make sense to me... Should I convert all my stuff in my main method to work on a generalized SObject map, which is a bit of a pain ? For instance, fields for specific objects aren't accessible, i.e my_map.get('key')[0].Name can't be used for instance

Any help on this is greatly appreciated

Thanks

1
  • 1
    You should avoid ever using Set<SObject> unless you are very familiar with the mechanics, to the point where you could explain them to a high schooler. – Adrian Larson Mar 29 '20 at 18:55
3

It's not easy to generalize this sort of thing since you cannot cast between Set<String> or Set<SObject> and Set<Object>. The best you can do is write infrastructural code that operates against a Map<String, Object>, where that "Object" can be a Set<String> or Set<SObject>, and split out the low level Set management into some form of "command pattern" delegate. For example:

interface SetManager {
    public void add(Object theSet, Object theValue);
    public Object createWith(Object theValue);
}

class StringSetManager implements SetManager {
    public void add(Object theSet, Object theValue) {
        ((Set<String>) theSet).add((String) theValue);
    }

    public Object createWith(Object theValue) {
        return new Set<String> { (String) theValue };
    }
}

class SObjectSetManager implements SetManager {
    ...
}

Then you might have the algorithmic code as:

void updateMap(SetManager manager, Map<String, Object> theMap, String key, Object theValue) {
    Object theSet = theMap.get(key);

    if (theSet != null) {
        manager.add(theSet, theValue);
    } else {
        theMap.put(key, manager.createWith(theValue));
    }
}

You would use this with a Map<String, Set<String>> like this:

Map<String, Set<String>> theMap = new Map<String, Set<String>>();
SetManager manager = new StringSetManager();
String key = 'abc';
String value = 'something';

updateMap(manager, (Map<String, Object>) theMap, key, value);

In this case, since what you are doing has so little common code that can be abstracted you can quickly see that this approach is somewhat pointless. However, this pattern can still be of use if the ratio of "manager" (or "command pattern") code to common algorithmic code is low (i.e. lots of algorithmic code and a little bit of extra code for the "command pattern").

As to the Set<SObject> vs Set<Opportunity> this can only be addressed by adding type casting (when you get the SObject out of the Set) or by using the more abstracted SObject API methods get(fieldName) and put(fieldName, value).

For the first option, you hold the Set as SObjects. Here you can add an Opportunity instance directly via theSet.add(opportunity) but when you get something out you need to take the set content into a List<SObject>, for example, then cast the content into Opportunity instances as you get them. Then you can access the Opportunity fields statically in your code as normal.

For the second option, once you get the SObject out you can use the more abstract get and put to manipulate fields on the object.

2
  • ok, i can see this may not reduce my code by that much. Regarding the last point, could you give more details about your answer "As to the Set<SObject> vs Set<Opportunity> this can only be addressed by adding type casting (when you get the SObject out of the Set) or by using the more abstracted SObject API methods get(fieldName) and put(fieldName, value)." Does that mean that I should generalize my code using the set<SObject> so that it works on SObject and not opportunity ? Or how could I convert in my "update_or_create_map" generalized method sometimes in account, in opp, etc.. ? – yeye Mar 30 '20 at 8:28
  • Take a look at the SObject documentation for get and put; if you use the SObject abstraction with these methods you can make this quite generic. It has the disadvantage of losing "compile time" field name and type checks though you can use the SObjectField-based variants instead to get better "compile time" handling at least for the field names. – Phil W Mar 30 '20 at 8:40

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.