2013-02-28 34 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