2013-02-28 21 views
7

對不起,模糊的標題;想不起如何更清楚地說出來。下面是提問的亮點:如何使用Enums的簡單性設計一個易於擴展的API?

亮點

  • 詢問有關ExifTool for Java庫的API設計的問題。
  • 這裏是an example當前API的樣子。
  • 作爲用戶,該API使用起來非常簡單,因爲您只需傳入想要返回的圖像元數據的Enums即可。
  • 作爲DEV,API有點糟糕,因爲您不能使用更多的Enum類型輕鬆擴展基類,以支持可能不直接在lib中支持的其他元數據。
  • 簡單地預先定義和支持「所有元數據」爲non-trivial

問題

鑑於設置信息,我所後,試圖找到一種方式來預先定義,人們通常根據它們的圖像所需的30個或40個最常見的元數據標記;現在一切都是defined as an Enum,但是這個類不能用這種方式擴展。

如果我選擇「每元數據標誌」路由,其可擴展性將會很簡單,但API的開箱即用性會差很多。

我將考慮這個庫的Java 8+的V2.0如果關閉提供一個非常美麗而簡單的解決方案,但除此之外,我更喜歡明顯,以保持其與多個系統兼容(Java的6/7)以內。

摘要

我對圖書館的目標是「簡單易用,擴展」 - 我覺得我已經釘在「簡單易用」方面與1.x的版本,但該庫是不容易可擴展的,我想在2.x系列中進行更正。

我一直坐在2.X版本了一年多的等待靈感罷工和它躲避我。我希望有人能夠發現我的錯誤,並且我可以以一種非常優雅的方式向前移動lib。

謝謝你的時間傢伙!

+0

如果你想要最簡單,最簡單,最靈活/強大的擴展,[property bag](http://steve-yegge.blogspot.com/2008/10/universal-design-pattern.html)去。 – 2013-02-28 22:03:18

+0

@MattBall我並不是那麼熟悉這個詞。看看這個鏈接的屬性部分,你的意思是像傳遞一個包含所有請求者需要的元數據的Key的Map,然後該庫爲所有這些鍵填充相關值並返回相同的Map? (不是一個壞主意......非常簡單和靈活) – 2013-02-28 22:12:31

回答

6

Java枚舉不可擴展,但它們可以實現接口。

你經常可以得到兩全其美通過限定供應商可以實現一個接口,並實現它,通常使用的情況下,該用戶將可以直接使用一個枚舉:

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

是用來接受原枚舉的情況下,現在應該採取的接口的任何實例的API。

可爲用戶提供使用相同的模式自己的實現:

public enum UncommonPet implements Pet { 
    LION; 

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

最後,也沒有要求所有的實現應該是枚舉,所以在更復雜的情況下,用戶可以選擇實現接口作爲羽翼豐滿類:

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

這是輝煌的,不知道枚舉將impl接口。我越來越沒有看到他們的本性如何看似靜止,但這將完全達到我想要的。 – 2013-03-01 23:56:11

2

這裏有一對夫婦的想法:

  1. 創建一個新界面來表示標籤並改進您的枚舉以實現它。或者可能調用新接口Tag,並將枚舉重命名爲TagsCommonTags。然後創建另一個實現接口的類,從而允許使用較少的通用標記。

    這種方法的好處是它不需要對結果進行很多更改,但它破壞了與舊版本庫的源代碼兼容性,並且稍微複雜一些。

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

    在你getImageMeta方法,而不僅僅是打電話Tag.forName,你就必須構建一個地圖標記名稱來Tag對象前手:

    ... 
    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. 還是Tag枚舉轉換爲簡單類有很多public static final字段:

    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 
    } 
    

    這將工作,除了th e部分,其中TAG_LOOKUP_MAP被初始化。在那裏,您可能需要再次列出所有標籤或可能使用反射來得到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); 
         } 
        } 
    } 
    

    但是,你甚至可能不會需要做到這一點,因爲你仍然需要進行同樣的更改到getImageMeta我剛纔提到,所以你的代碼實際上不需要調用Tag.forName。圖書館的用戶可能已經在使用它了。

    這種方法的最大優勢在於它保持了源代碼兼容性,從外部看起來基本相同(例如,用戶仍然使用Tag.ISO),用戶可以通過簡單地執行new Tag("ColorMode", Integer.class)來創建新標籤。缺點是它仍然會破壞二進制兼容性,並且在開發方面保持一點點混亂。

我敢肯定還有其他的選擇,但是有兩個發生在我身上。

+0

Matts,真的很感激你去的超越與impls非常具體的API - 優秀的細節! – 2013-03-01 23:57:08