Карта Карт разных типов

Мне нужно инициализировать пару объектов разных типов от определений XML до bean-компонентов. У меня есть структура каталогов:

data \
|_ plant \
|  |_ pine.xml
|  |_ cucumber.xml
|_ animal \
|  |_dog.xml
|  |_cat.xml
|_ fungus \
...

Я хочу хранить его упорядоченно на двухуровневой карте: карта первого уровня содержит карты второго уровня. Каждая карта второго уровня содержит объекты одного типа (класса растений/животных/грибов...). Каждый класс расширяет класс Dict, который имеет некоторые общие поля и методы (name/getName()/setName(), desc/getDesc()/setDesc(), toString(), equals() и тому подобное.

Теперь, чтобы облегчить создание всех этих объектов, я хочу иметь фабричный метод Dict.fabricateAll(). И вот проблема: как сделать карты второго уровня, чтобы у них были точные параметры типа. Мне нужны не HashMap‹String, Dict›, а HashMap‹String, Plant›, HashMap‹String, Animal› и т. д.

На данный момент у меня есть следующий код:

public static Map<String, Map<String, ? extends Dict>> fabricateAll() {
    File topDir = new File(Dict.defDir);
    File[] subDirs = topDir.listFiles();
    Map<String, Map<String, ? extends Dict>> dicts = new HashMap<>(subDirs.length);

    String type;
    String handle;
    Map<String, ? extends Dict> dict;

    for (File subDir : subDirs) {
        type = subDir.getName(); 
        File[] xmlFiles = subDir.listFiles(new FilenameFilter() {public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".xml"); }} );
        dict = new HashMap<>(xmlFiles.length);

        for (File defFile : xmlFiles) {
            handle = defFile.getName();
            handle = handle.substring(0, handle.lastIndexOf('.')-1);

            try {
                Class c = Class.forName(type);
                Constructor bob = c.getConstructor(Class.forName("java.lang.String"));
                dict.put(handle, bob.newInstance(handle));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        dicts.put(type, dict);
    }
    return dicts;
}

Но есть проблема со строкой: dict.put(handle, bob.newInstance(handle));

Сообщение об ошибке: "метод, указанный в интерфейсе Map‹K,V›, не может быть применен к заданным типам. Требуется: String, CAP#1 Найдено: String,Object

Да, этот newInstance() генерирует объекты и, вероятно, должно быть какое-то приведение, но к чему? Я не хочу здесь просто "(Dict)", я бы предпочел "(Растение)", "(Животное)"... в зависимости от ситуации. Как это сделать?


person Forseti    schedule 24.02.2013    source источник
comment
Вы возвращаете Map<String, Map<String, ? extends Dict>>. Таким образом, клиентский код не может знать, содержат ли карты растения, кошек или что-то еще. Все, что он знает, это то, что они являются экземплярами Dict. Так закиньте его в Dict, и все будет хорошо. Тем не менее, я бы использовал для этого не отражение, а фабрики (одна фабрика на тип Dict), которые позволили бы делать то, что вы хотите.   -  person JB Nizet    schedule 24.02.2013
comment
Но Animal может run(), а Plant — нет. Если я приведу его к Dict, у меня не будет возможности вызвать run(), кроме ручного приведения к Animal. Что касается подхода «одна фабрика на Dict»: вы имеете в виду, что у меня должен быть «общедоступный статический сборщик (дескриптор строки)» в каждом из подтипов Dict и вместо вызова «bob.newInstance (дескриптор)» для выполнения: dict.put (дескриптор , c.getMethod(fabricate, Class.forName(java.lang.String))) ? Это дает ту же ошибку.   -  person Forseti    schedule 24.02.2013
comment
Когда у вас есть Map<String, ? extends Dict>, вы все равно не можете узнать, что содержит карта. Все, что вы можете сделать, это проверить, является ли значение экземпляром растения или животного (используя instanceof), и преобразовать значение в растение или животное. Итак, что вы получаете, делая карту Map<String, Animal>, поскольку вы возвращаете ее как Map<String, ? extends Dict>. Ваша фабричная идея действительно является решением. Вам понадобится метод для возврата Plant или Animal (в зависимости от класса), и вам потребуется, чтобы карта была типа Map<String, Plant> или типа Map<String, Animal>, в зависимости от каталога.   -  person JB Nizet    schedule 24.02.2013


Ответы (1)


Приведите объект к Dict. В этом преимущество полиморфизма — вам не нужно обращаться к конкретным подклассам. Вы можете написать свой код со ссылками только на один базовый класс, Dict. Это дает вам:

  • а) Слабая связь - нет зависимостей во время компиляции от конкретных подклассов
  • б) Расширяемость — вы можете добавлять подклассы без изменения этого кода.

Да, есть способы добиться того, о чем вы просили (на ум приходит большой оператор switch), но ничего такого, что я бы рекомендовал.

person EJK    schedule 24.02.2013