7

抱歉标题含糊;想不出如何更清楚地表达它。以下是问题的重点:

强调

  • 询问有关ExifTool for Java库的 API 设计问题。
  • 这是当前 API 外观的示例。
  • 作为USER,该 API 使用起来非常简单,因为您只需传入 Enums 即可获取您想要返回的图像元数据。
  • 作为DEV,API 有点糟糕,因为您无法轻松地使用更多 Enum 类型扩展基类以支持可能不直接在 lib 中支持的其他元数据。
  • 简单地预先定义和支持“所有元数据”并非易事

问题

鉴于这些设置信息,我想要找到一种方法来预定义人们通常希望从图像中获得的 30 或 40 个最常见的元数据标志;现在一切都被定义为 Enum,但该类不能以这种方式扩展。

如果我采用“Class-per-Metadata-flag”路线,可扩展性会很简单,但开箱即用的 API 会不太友好。

如果闭包提供了一个非常漂亮和简单的解决方案,我将考虑制作这个库的 v2.0 Java 8+,但否则我显然更愿意让它与更多系统(Java 6/7)兼容而不是更少。

概括

我对该库的目标是“易于使用和扩展”——我觉得我已经在 1.x 版本中确定了“易于使用”的方面,但该库不容易扩展,我想在2.x 系列。

我已经在 2.x 版本上等待了一年多的时间来等待灵感来袭,但它却让我望而却步;我希望有人能发现我的错误,并且我可以以一种非常优雅的方式向前推进 lib。

谢谢你们的时间!

4

2 回答 2

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;
    }
}
于 2013-02-28T23:02:23.650 回答
2

这里有几个想法:

  1. 创建一个新接口来表示一个标签并改造你的枚举来实现它。或者也许调用新接口Tag,并将枚举重命名为Tagsor CommonTags。然后创建另一个实现接口的类,允许使用不太常见的标签。

    这种方法的好处是它不需要对您进行大量更改,但它破坏了与旧版本库的源代码兼容性,并且稍微复杂一些。

    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.forNameTag

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

    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). 缺点是它仍然破坏了二进制兼容性,并且在开发方面维护起来有点混乱。

我敢肯定还有其他选择,但我想到了两个。

于 2013-02-28T23:12:03.410 回答