2013-02-28 9 views
7

Przepraszamy za niewyraźny tytuł; nie mógł wymyślić, jak wyrazić to lepiej. Oto najważniejsze z pytań:Jak zaprojektować łatwo rozszerzalny interfejs API z prostotą Enums?

Highlights

  • Zadawanie wzoru API pytanie o bibliotece ExifTool for Java.
  • Oto an example tego, jak wygląda bieżący interfejs API.
  • Jako USER, interfejs API jest bardzo prosty w użyciu, ponieważ po prostu przekazujesz w Enums dla metadanych obrazu, które chcesz odzyskać.
  • Jako DEV, API jest nieco do bani, ponieważ nie można łatwo rozszerzyć klasy bazowej o więcej typów Enum, aby obsługiwać dodatkowe metadane, które mogą nie być obsługiwane bezpośrednio w bibliotece.
  • Po prostu wstępnie zdefiniowano i dodano "wszystkie metadane" to non-trivial.

Pytanie

Biorąc pod uwagę, że informacje dotyczące instalacji, co ja po próbuje znaleźć sposób, aby wstępnie określić najczęstsze flagi 30 lub 40 metadanych, że ludzie zazwyczaj mają ze swoich obrazów; teraz wszystko jest defined as an Enum, ale klasa nie jest rozszerzalna w ten sposób.

Jeśli przejdę do opcji "Class-per-Metadata-Flag", rozszerzalność będzie prosta, ale API będzie o wiele mniej przyjazne w użyciu po wyjęciu z pudełka.

Rozważę zrobienie v2.0 tej biblioteki Java 8+, jeśli zamknięcia oferują naprawdę piękne i proste rozwiązanie, ale poza tym zdecydowanie wolałbym, aby był kompatybilny z większą liczbą systemów (Java 6/7) niż mniej.

Podsumowanie

Moje cele dla biblioteki są „proste w obsłudze i rozszerzyć” - czuję się przybity „prosty w obsłudze” aspekt w wydaniu 1.x, ale biblioteka nie jest łatwo Rozszerzalny i chciałbym to poprawić w serii 2.x.

Siedziałem w wydaniu 2.x przez ponad rok, czekając na inspirację do strajku i to mi umknęło; Mam nadzieję, że ktoś może dostrzec mój błąd i mogę przenieść libę do przodu w naprawdę elegancki sposób.

Dziękuję za czasy!

+0

Jeśli chcesz najprostszy, najłatwiejszy i najbardziej elastyczny/potężny do rozszerzenia, [właściwość worka] (http://steve-yegge.blogspot.com/2008/10uniwersalny-design-pattern.html) jest sposobem iść. –

+0

@MattBall Nie jestem zaznajomiony z tym terminem. Patrząc na sekcję Właściwość tego linku, masz na myśli coś w rodzaju zwykłego przejścia w Mapę ze wszystkimi kluczami reprezentującymi metadane, których żąda osoba żądająca, a następnie biblioteka wypełnia powiązane wartości dla wszystkich tych kluczy i zwraca tę samą mapę? (nie jest to zły pomysł ... bardzo prosty i elastyczny) –

Odpowiedz

6

Wyliczenia Java nie są rozszerzalne, ale mogą implementować interfejsy.

Często można uzyskać najlepsze z obu światów poprzez zdefiniowanie interfejsu że operatorzy mogą wdrożyć i enum, który implementuje go i zawiera powszechnie stosowane instancji, że użytkownicy będą mogli korzystać bezpośrednio:

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

Interfejs API, który był używany do akceptowania wystąpień oryginalnego wyliczenia, powinien teraz zająć dowolne wystąpienie interfejsu.

Użytkownicy mogą udostępniać własne implementacje z wykorzystaniem tego samego wzoru:

public enum UncommonPet implements Pet { 
    LION; 

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

Wreszcie, nie ma wymogu, aby wszystkie implementacje powinny być teksty stałe, więc w bardziej złożonych przypadkach użytkownik może wybrać, aby implementować interfejs w postaci pełnoprawnym klasa:

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

To jest genialne, nie miał pojęcia, że ​​enumy będą implikować interfejsy. Odkładałam słuchawkę, nieważne, jak pozornie statyczne były w ich naturze, ale to by spełniło dokładnie to, czego chciałem. –

2

Oto kilka pomysłów:

  1. Utwórz nowy interfejs do oznaczania tagu i zmodyfikuj swój wylicznik, aby go zaimplementować. A może zadzwoń do nowego interfejsu Tag i zmień nazwę enum na Tags lub CommonTags. Następnie stwórz kolejną klasę, która implementuje interfejs, pozwalając na mniej typowe znaczniki.

    Zaletą tego podejścia jest to, że nie wymaga on wielu zmian na końcu, ale zrywa zgodność źródła ze starszymi wersjami biblioteki i jest nieco bardziej skomplikowany.

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

    W swojej metodzie getImageMeta, zamiast po prostu wywołanie Tag.forName, trzeba by skonstruować mapę nazw znaczników do Tag obiektów przed strony:

    ... 
    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. Albo przekształcić Tag Do wyliczenia prosta klasa z dużą ilością public static final dziedzinach:

    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 
    } 
    

    będzie to działać z wyjątkiem th e część, gdzie zainicjowano TAG_LOOKUP_MAP. Tam, albo trzeba wymienić wszystkie tagi ponownie lub może użyć refleksji uzyskać wszystkie pola na 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); 
         } 
        } 
    } 
    

    Jednakże, może nawet nie trzeba tego robić, ponieważ trzeba jeszcze wprowadzić taką samą zmianę do getImageMeta Wspomniałem wcześniej, więc twój kod nie będzie musiał zadzwonić pod numer Tag.forName. Użytkownicy biblioteki mogli go jednak używać.

    Do tego typu rozwiązań jest to, że zachowuje zgodność ze źródłami, wygląda tak samo z zewnątrz (użytkownicy nadal używają na przykład Tag.ISO), a użytkownicy mogą tworzyć nowe znaczniki, robiąc po prostu new Tag("ColorMode", Integer.class). Minusem jest to, że nadal łamie binarną kompatybilność i jest trochę bardziej niechlujnie do utrzymania po stronie programowania.

Jestem pewien, że są inne opcje, ale są dwie, które przyszło mi do głowy.

+0

Matts, naprawdę doceniam, że wykraczasz poza granice, a implanty są bardzo specyficzne dla API - doskonałe szczegóły! –

Powiązane problemy