Как сделать так, чтобы аргументы типа оператора Diamond были динамическими в Java?

У меня есть следующий интерфейс

public interface Splitter<T, V> {
    V[] split(T arg);
}

Ниже приведена реализация метода фабрики, которую я использую, чтобы получить реализацию Splitter.

Реализация метода фабрики

public static <T, V> Splitter<T, V> getSplitter(Class<T> key1, Class<V> key2) {

    if (key1 == Company.class && key2 == Department.class)
        return (Splitter<T, V>) new CompanySplitterImpl();

    // more cases
}

Ниже мой звонок на стороне клиента, который хорошо компилируется

Splitter<Company, Department> split = getSplitter(Company.class, Department.class);

Я хочу избежать тесной связи кода на стороне клиента с реализацией. Есть ли способ избежать жестко закодированных параметров типа, т.е. избежать использования компании и отдела (Splitter<Company, Department>) на стороне вызываемого абонента и использовать вместо этого некоторую переменную? Есть ли выход, через который они могут быть загружены из какого-либо внешнего файла свойств?

К вашему сведению: я не уверен в его целесообразности на Java?

4 ответа

Одна вещь, которую вы могли бы сделать, - это чтобы ваша фабрика ничего не знала о конкретных реализациях, и вместо этого попросила себя зарегистрироваться в ней (или содержать предварительно определенный список) и спросить каждую реализацию, может ли она обрабатывать типы или нет. Например, для заданного списка, как в примере выше:

public class SplitterFactory {
    private Set<Splitter> splitters = new HashSet<>() {{
        add(new CompanySplitterImpl());
    }};

    public static <T, V> Splitter<T, V> getSplitter(Class<T> key1, Class<V> key2) 
    {
        for (Splitter splitter : splitters) {
            if (splitter.canAccept(key1, key2)) {
                return splitter;
        }       
        // no matched splitter
    }
}

Очевидно, что это очень наивное решение, и вы могли бы реализовать поиск более эффективно. Если вы не знаете свои типы во время компиляции, у вас также может быть механизм регистрации с фабрикой для включения новых во время выполнения. Поскольку теперь сам Splitter отвечает за то, чтобы сообщать, какие типы он может обрабатывать, он полностью расширяем.

Вы можете создать простой класс карты, в котором вы можете перечислить их до:

public final class SplitterMap {

    private final List<SplitterType<?, ?>> list = new ArrayList<>();

    private class SplitterType<T, V> {

        private final Class<T> key1;
        private final Class<V> key2;
        private final Class<? extends Splitter<T, V>> clazz;

        private SplitterType(Class<?> key1, Class<?> key2, Class<? extends Splitter<T, V> clazz) {
            this.key1 = key1;
            this.key2 = key2;
            this.clazz = clazz;
        }

        private boolean matches(Class<?> key1, Class<?> key2) {
            return this.key1 == key1 && this.key2 == key2;
        }
    }

    public <T, V> void put(Class<T> key1, Class<V> key2, Class<? extends Splitter<T, V> clazz) {
        list.add(new SplitterType<T, V>(key1, key2, clazz));
    }

    public <T, V> Splitter<T, V> get(Class<T> key1, Class<V> key2) {
        for (SplitterType<?, ?> type : list) {
            if (type.matches(key1, key2)) {
                try {
                    return ((SplitterType<T, V>) type).clazz.newInstance();
                } catch (Exception e) {
                }
            }
        }
        return null; // not found
    }
}

Тогда вы можете просто сделать:

SplitterMap map = new SplitterMap();
map.put(Company.class, Department.class, CompanySplitterImpl.class);
Splitter<Company, Department> splitter = map.get(Company.class, Department.class);

Не очень хороший способ, но одним из способов будет:

String companyClass = "Company";
String departmentClass = "Department";
Splitter split = getSplitter(Class.forName(companyClass), Class.forName(departmentClass));//raw splitter
System.out.println(split.split(new Company()));//you could use reflection here to create instance from companyClass String.

Во-первых, я предполагаю, что вы хотите что-то вроде

Splitter<Company, Department> s = Splitters.getSplitter()

Что невозможно без размышлений, из-за

  1. стирание типа
  2. Перегрузка возвращаемого типа пока недоступна в Java

Во-вторых, вы злоупотребляете шаблоном FactoryMethod. Который должен выглядеть больше так:

interface Splitter<T, V> {
    V[] split(T arg);
}

interface SplitterFactory {
    <T, V> Splitter<T, V> getSplitter();
}

class CompanySplitterFactory implements SplitterFactory {
    @Override
    public Splitter<Company, Department> getSplitter() {
        return new CompanySplitterImpl();
    }
}
Другие вопросы по тегам