2013-02-28 9 views
7

Ci scusiamo per il titolo vago; non potevo pensare a come esprimerlo più chiaramente. Ecco i punti salienti delle domande:Come progettare un'API facilmente estendibile con la semplicità di Enums?

evidenza

  • Chiedere una questione di progettazione API sulla libreria ExifTool for Java.
  • Ecco il numero an example dell'aspetto dell'API corrente.
  • In qualità di UTENTE, l'API è semplicissimo da utilizzare perché basta passare in Enum per i metadati dell'immagine che si desidera recuperare.
  • Come DEV, l'API in qualche modo fa schifo perché non è possibile estendere facilmente la classe base con più tipi Enum per supportare metadati aggiuntivi che potrebbero non essere supportati direttamente nella lib.
  • Semplicemente pre-definendo e supportando "tutti i metadati" è non-trivial.

Domanda

Dato che le informazioni di installazione, quello che sto dopo sta cercando di trovare un modo per predefinire i 30 o 40 bandiere di metadati più comuni che la gente di solito vogliono dalle loro immagini; in questo momento tutto è defined as an Enum, ma la classe non è estensibile in questo modo.

Se si esegue il percorso "Class-per-Metadata-flag", l'estensibilità sarà semplice, ma l'API sarà molto meno intuitiva da utilizzare immediatamente.

Prenderò in considerazione la possibilità di creare v2.0 di questa libreria Java 8+ se le chiusure offrono una soluzione davvero bella e semplice, ma altrimenti preferirei ovviamente mantenerlo compatibile con più sistemi (Java 6/7) rispetto a meno.

Sommario

I miei obiettivi per la libreria sono "semplice da usare ed estendere" - sento di aver inchiodato la "semplice da usare" aspetto con il rilascio 1.x, ma la libreria non è facilmente estensibile e vorrei correggerlo nella serie 2.x.

Sono stato seduto sull'uscita 2.x per oltre un anno in attesa dell'ispirazione per colpire e mi ha eluso; Spero che qualcuno possa individuare il mio errore e posso spostare la libreria in un modo davvero elegante.

Grazie per il momento ragazzi!

+0

Se si desidera la più semplice, più semplice e più flessibile/potente da estendere, [property bag] (http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html) è il modo andare. –

+0

@MattBall Non sono così familiare con quel termine. Guardando la sezione Proprietà di quel collegamento, intendi qualcosa come il semplice passaggio in una mappa con tutte le chiavi che rappresentano i metadati richiesti dal richiedente e quindi la libreria riempie i valori associati per tutte quelle chiavi e restituisce la stessa mappa? (non una cattiva idea ... molto semplice e flessibile) –

risposta

6

Le enumerazioni Java non sono estensibili, ma possono implementare interfacce.

Spesso è possibile ottenere il meglio dei due mondi definendo un'interfaccia che i provider possono implementare, e un enum che lo implementa e contiene comunemente usato istanze che gli utenti saranno in grado di utilizzare direttamente:

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; 
    } 
} 

L'API utilizzata per accettare le istanze dell'enumerazione originale ora dovrebbe prendere qualsiasi istanza dell'interfaccia.

Gli utenti possono fornire le loro implementazioni che utilizzano lo stesso schema:

public enum UncommonPet implements Pet { 
    LION; 

    @Override 
    public String talk() { 
     return "Roar!"; 
    } 
} 

Infine, non v'è alcun obbligo che tutte le implementazioni dovrebbero essere le enumerazioni, così nei casi più complessi l'utente può scegliere di implementare l'interfaccia come a tutti gli effetti di classe:

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; 
    } 
} 
+0

Questo è geniale, non avevo idea che l'enumerazione avrebbe imploso le interfacce. Non stavo rimettendo in discussione quanto apparentemente statici fossero nella loro natura, ma questo avrebbe realizzato esattamente quello che volevo. –

2

Ecco un paio di idee:

  1. Crea una nuova interfaccia per rappresentare un tag e modifica l'enum per implementarlo. O forse chiama la nuova interfaccia Tag e rinomina l'enum a Tags o CommonTags. Quindi crea un'altra classe che implementa l'interfaccia, consentendo tag meno comuni.

    Il vantaggio di questo approccio è che non richiede molte modifiche sul proprio terminale, ma interrompe la compatibilità con le vecchie versioni della libreria ed è un po 'più complicato.

    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; 
        } 
    } 
    

    nel metodo getImageMeta, invece di chiamare Tag.forName, che avrebbe dovuto costruire una mappa di nomi di tag per Tag oggetti prima mano:

    ... 
    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]); 
    ... 
    
  2. o convertire il Tag enum ad un semplice classe con un sacco di public static final campi:

    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 
    } 
    

    Questo funziona ad eccezione di th e parte dove viene inizializzato TAG_LOOKUP_MAP. Lì, si sia bisogno di elencare di nuovo tutti i tag o forse utilizzare la reflection per ottenere tutti i campi su 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); 
         } 
        } 
    } 
    

    Tuttavia, non si può nemmeno bisogno di fare questo, dal momento che è comunque necessario apportare la stessa modifica a getImageMeta ho menzionato in precedenza, quindi il tuo codice in realtà non ha bisogno di chiamare Tag.forName. Tuttavia, gli utenti della biblioteca potrebbero averlo usato.

    Upside a questo approccio è che esso mantiene la compatibilità fonte, guarda soprattutto lo stesso dall'esterno (utenti ancora utilizzare Tag.ISO, per esempio), e gli utenti possono creare nuovi tag semplicemente facendo new Tag("ColorMode", Integer.class). Il rovescio della medaglia è che rompe ancora la compatibilità binaria ed è un po 'pacchiano da mantenere sul lato dello sviluppo.

Sono sicuro che ci sono altre opzioni, ma ce ne sono state due.

+0

Matts, apprezzo molto che tu stia andando oltre e con il fatto che l'impls sia molto specifico per l'API - dettagli eccellenti! –