How to create an easily extensible API with Enums simplicity?

Sorry for the vague name; I couldn’t figure out how to say it more clearly. Here are the highlights of the questions:

The main

  • Ask a question about the design of the ExifTool Library API for Java .
  • Here is an example of what the current API looks like.
  • As a USER, the API is very easy to use because you simply go to Enums for the metadata of the images you want to return.
  • As a DEV API, it sucks a bit because you cannot easily extend the base class with additional Enum types to support additional metadata that may not be supported directly in lib.
  • Just a preliminary definition and support of "all metadata" is not trivial .

Question

Given this setup information, I'm trying to find a way to pre-define the 30 or 40 most common metadata flags that people usually want from their images; Now everything is defined as Enum , but the class does not expand in this way.

If I go along the “Class Route for Metadata” route, extensibility will be simple, but the API will be much less friendly to use out of the box.

I will think about making v2.0 of this Java 8+ library if closing offers a really nice and simple solution, but otherwise I would prefer to keep it compatible with more systems (Java 6/7) than less.

Summary

My library goals are “easy to use and expandable” - I feel like I have attached the “easy to use” aspect with the 1.x release, but the library is not easily extensible, and I would like to fix that in the 2.x series.

I sat on release 2.x for over a year, waiting for inspiration to hit, and it eluded me; I hope someone can notice my mistake and I can move forward really elegantly.

Thanks for the time guys!

+7
source share
2 answers

Java variables are not extensible, but can implement interfaces.

You can often get the best of both worlds by defining an interface that providers can implement, and an enumeration that implements it, and contains frequently used instances that users can use directly:

public interface Pet { public String talk(); } 
 public enum CommonPet implements Pet { CAT("Meow!"), DOG("Woof! Woof!"); private final String cry; CommonPet(String cry) { this.cry = cry; } @Override public String talk() { return cry; } } 

The API that was used to receive instances of the source enumeration should now take any instance of the interface.

Users can provide their own implementations using the same template:

 public enum UncommonPet implements Pet { LION; @Override public String talk() { return "Roar!"; } } 

Finally, there is no requirement that all implementations be enumerations, so in more complex cases, the user can implement the interface as a full-fledged class:

 public class Parrot implements Pet { private String phrase = "Pieces of eight!"; @Override public String talk() { return phrase; } public void teach(String phrase) { this.phrase = phrase; } } 
+6
source

Here are a couple of ideas:

  • Create a new interface for presenting the tag and rebuild it to implement it. Or perhaps call the new Tag interface and rename enum to Tags or CommonTags . Then create another class that implements the interface, allowing you to use less common tags.

    The advantage of this approach is that it does not require big changes at your end, but it breaks the source compatibility with older versions of the library and is a bit more complicated.

     public interface Tag { String getName(); Class<?> getType(); } public enum Tags implements Tag { // mostly same as before } public class OtherTag implements Tag { private String name; private Class<?> type; public OtherTag(String name, Class<?> type) { this.name = name; this.type = type; } @Override public String getName() { return name; } @Override public Class<?> getType() { return type; } } 

    In your getImageMeta method, instead of simply calling Tag.forName you will need to create a tag name map before the Tag objects:

     ... Map<String, Tag> tagMap = new HashMap<String, Tag>(); for (Tag tag: tags) tagMap.put(tag.getName(), tag); ... while ((line = streams.reader.readLine()) != null) { String[] pair = TAG_VALUE_PATTERN.split(line); if (pair != null && pair.length == 2) { // Determine the tag represented by this value. Tag tag = tagMap.get(pair[0]); ... 
  • Or convert the Tag enumeration to a simple class with many public static final fields:

     public class Tag { public static final Tag ISO = new Tag("ISO", Integer.class); public static final Tag APERTURE = new Tag("ApertureValue", Double.class); public static final Tag WHITE_BALANCE = new Tag("WhiteBalance", Integer.class); ... // almost everything else the same // Tag constructor should now be public } 

    This will work, except for the part where TAG_LOOKUP_MAP initialized. There you either need to list all the tags again, or use reflection to get all the fields on the Tag :

     private static final Map<String, Tag> TAG_LOOKUP_MAP; static { for (Field field: Tag.class.getFields()) { if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) { Tag tag = (Tag) field.get(null); TAG_LOOKUP_MAP.put(tag.getName(), tag); } } } 

    However, you may not need to do this, since you still need to make the same change in getImageMeta that I mentioned earlier, so you do not need to name the Tag.forName code. Perhaps users of the library used it.

    The surface to this approach is that it maintains compatibility with the source code, which usually looks the same from the outside (users still use Tag.ISO , for example), and users can create new tags just by making a new Tag("ColorMode", Integer.class) . The downside is that it still interrupts binary compatibility, and it is a bit messy for development-side support.

I am sure there are other options, but two have come to me.

+2
source

All Articles