PullXorPush

From APIDesign

Revision as of 13:48, 16 June 2017 by JaroslavTulach (Talk | contribs)
Jump to: navigation, search

The Extreme Advice Considered Harmful chapter of TheAPIBook talks about symmetry. It describes how harmful seeking for full symmetry at all costs can be. The chapter argues that there often isn't a need for both GettersAndSetters, that in many cases just getters in an API are enough. Similar example can be given for pull vs. push in APIs - it is unlikely you need both of them at once.

Push like Example

Imagine you have a translation service and you'd like to register dictionaries to it. The push version could be based on following API:

public final class Translator {
  private Translator() {
  }
 
  public static void registerTranslation(String fromLanguage, String toLanguage, Map<String,String> vocabulary) {
     // ...
  }
}

in a system of such kind you require all translation modules (it probably makes sense to assume the translations are developed independently, right?) to eagerly - e.g. as early as possible - register themselves into the system and provide necessary details. From which language they translate words, to which language and what is the vocabulary they can handle. This is probably too premature - better to ask when needed.

Pull like Approach

Why register everything at the beginning? It is not even clear the registered translation will really be needed. In such open-ended system, it is very likely better to try a pull-like pattern:

public interface Translate {
  public boolean canFromLanguage(String language);
  public boolena canToLanguage(String language);
  public String translate(String word);
 
  public static void registerTranslation(Translate t) {
    // ...
  }
}

This has some benefits (and it can even be improved to work without the registerTranslation method with Lookup based registration or even more with AnnotationProcessors) over the push version. One doesn't have to load languages until they are really needed. Also one can translate unknown words - if the word to translate is in plural, it is the language implementation can make additional adjustments and convert it to singular form, translate it and then (in the target language) translate it back to plural. Or one can translate even words that don't make sense - by looking up the closest alternative.

This is the value of the pull-approach - one gets a callback and can adjust to it when needed.

Don't Pull & Push at Once!

The thing to remember is that (unless there are threading issues), it is always possible to simulate the push approach with the pull one. E.g. it makes no sense to design an API that supports both - prefer the pull approach as the more flexible one. One can always turn push into pull:

public final class PushLikeTranslate implements Translate {
  static {
    Translate.registerTranslation(new PushLikeTranslate());
  }
 
  public boolean canFromLanguage(String language) {
    // use the data from pushTranslation
  }
  public boolean canToLanguage(String language) {
    // use the data from pushTranslation
  }
 
  public String translate(String word) {
    // use the data from pushTranslation
  }
 
 
  public static void pushTranslation(String fromLanguage, String toLanguage, Map<String,String> vocabulary) {
    // register the vocabulary for further use
  }
}

It is easy to mimic push approach when you have a chance to to register for a pull. Just like avoiding both SettersAndGetters, never support both pull & push in the same core API.

Personal tools
buy