java Enums की सादगी के साथ एक आसानी से एक्स्टेंसिबल एपीआई कैसे डिजाइन किया जा सकता है?




api design (2)

अस्पष्ट शीर्षक के लिए क्षमा करें; यह कैसे और अधिक स्पष्ट रूप से शब्द के बारे में नहीं सोच सकता था यहां सवालों के मुख्य आकर्षण हैं:

हाइलाइट

  • जावा पुस्तकालय के लिए ExifTool के बारे में एक एपीआई डिजाइन प्रश्न पूछना
  • वर्तमान एपीआई कैसा दिखता है इसका एक उदाहरण यहाँ है।
  • एक यूजर के रूप में, एपीआई का उपयोग करना अति सरल है क्योंकि आप इमेज मेटाडाटा के लिए सिर्फ एन्मौसम में वापस आते हैं, जिसे आप चाहते हैं।
  • एक DEV के रूप में, एपीआई कुछ हद तक बेकार है क्योंकि आप अतिरिक्त मेटाडेटा का समर्थन करने के लिए अधिक Enum प्रकारों के साथ आसानी से बेस क्लास नहीं बढ़ा सकते हैं जो सीधे लिब में समर्थित नहीं हो सकते हैं।
  • बस पूर्व-परिभाषित और समर्थन " सभी मेटाडेटा " गैर तुच्छ है

सवाल

यह देखते हुए कि स्थापना की जानकारी, जो मैं उसके बाद हूं वह 30 या 40 सबसे आम मेटाडेटा झंडे को परिभाषित करने का एक तरीका ढूंढने की कोशिश कर रहा है, जो लोग आम तौर पर उनकी छवियों से चाहते हैं; अभी सब कुछ एक एन्यूएम के रूप में परिभाषित किया गया है , लेकिन कक्षा इस तरह से विस्तारणीय नहीं है।

अगर मैं "क्लास-प्रति-मेटाडेटा-फ्लैग" मार्ग पर जाता हूं, तो सहजता सरल होगी, लेकिन बॉक्स से बाहर का उपयोग करने के लिए एपीआई बहुत कम अनुकूल होगा।

मैं इस लाइब्रेरी जावा 8 + की v2.0 करने पर विचार करूँगा यदि क्लोजर वास्तव में एक सुंदर और सरल समाधान प्रदान करता है, लेकिन अन्यथा मैं इसे कम से कम अधिक सिस्टम (जावा 6/7) के साथ संगत रखना पसंद करता हूं

सारांश

पुस्तकालय के लिए मेरे लक्ष्य "उपयोग करने और विस्तार करने में आसान" हैं - मुझे लगता है कि मैंने 1.x रिहाई के साथ "उपयोग करने के लिए सरल" पहलू को पकड़ा है, लेकिन लाइब्रेरी आसानी से विस्तारणीय नहीं है और मैं इसे सही करना चाहता हूं 2.x श्रृंखला

मैं प्रेरणा से हड़ताल के लिए एक साल से अधिक समय से 2.x रिहाई के लिए बैठा हूं और मुझे यह उम्मीद नहीं है; मैं उम्मीद कर रहा हूं कि कोई मेरी गलती को देख सकता है और मैं वास्तव में सुरुचिपूर्ण तरीके से लिब आगे बढ़ सकता हूं।

समय के लिए धन्यवाद लोग!


यहाँ कुछ विचार हैं:

  1. किसी टैग का प्रतिनिधित्व करने के लिए एक नया इंटरफ़ेस बनाएं और इसे लागू करने के लिए अपने एंमान को पुन: आरक्षित करें। या हो सकता है कि नए इंटरफ़ेस Tag कॉल करें और Tags या 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 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
    }

    यह उस भाग को छोड़कर काम करेगा जहां 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 उपयोग Tag.ISO , उदाहरण के लिए), और उपयोगकर्ता केवल new Tag("ColorMode", Integer.class) करके नया टैग बना सकते हैं। । डाउनसाइड यह अभी भी बाइनरी संगतता को तोड़ता है और यह विकास पक्ष पर बनाए रखने के लिए थोड़ा सा संदेश है।

मुझे यकीन है कि वहाँ अन्य विकल्प हैं, लेकिन दो मेरे लिए हुआ है


जावा enums एक्स्टेंसिबल नहीं हैं, लेकिन वे इंटरफेस लागू कर सकते हैं।

आप अक्सर इंटरफेस को परिभाषित करके दोनों दुनिया का सर्वश्रेष्ठ प्राप्त कर सकते हैं जो प्रदाताओं को कार्यान्वित कर सकते हैं, और एक एएनआईएम जो इसे लागू करता है और आमतौर पर उपयोग किए जाने वाले उदाहरणों में उपयोगकर्ता सीधे उपयोग करने में सक्षम होंगे:

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

एपीआई जो मूल enum के उदाहरणों को स्वीकार करने के लिए प्रयोग किया जाता था अब इंटरफ़ेस का कोई भी उदाहरण लेना चाहिए।

उपयोगकर्ता एक ही पैटर्न का उपयोग करके अपने स्वयं के लागूकरण प्रदान कर सकते हैं:

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




exif