Создание метода фабрики в Java, который не полагается если еще

В настоящее время у меня есть метод, который действует как фабрика на основе данной Строки. Например:

public Animal createAnimal(String action)
{
    if (action.equals("Meow"))
    {
        return new Cat();
    }
    else if (action.equals("Woof"))
    {
        return new Dog();
    }

    ...
    etc.
}

То, что я хочу сделать, избегают всего, если еще проблема, когда список классов растет. Я полагаю, что у меня должно быть два метода, тот, который регистрирует Строки к классам и другому, который возвращает класс на основе Строки действия.

Что хороший путь состоит в том, чтобы сделать это в Java?

38
задан Brad 8 August 2010 в 13:28
поделиться

8 ответов

. То, что вы сделали, вероятно, лучший способ сделать это, пока не станет доступна строка включения. ( Edit 2019: Доступен переключатель на строке - используйте это.)

Вы можете создавать фабричные объекты и карту из строк к ним. Но в текущей версии Java это становится немного подробным.

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,AnimalFactory>() {{
        put("Meow", new AnimalFactory() { public Animal create() { return new Cat(); }});
        put("Woof", new AnimalFactory() { public Animal create() { return new Dog(); }});
    }});

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw new EhException();
    }
    return factory.create();
}

На момент написания этого ответа функции, предназначенные для JDK7, могли сделать код таким, как показано ниже. Как выяснилось, лямбды появились в Java SE 8, и, насколько мне известно, в плане литералов карты нет. ( Отредактировано в 2016 году )

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap = {
    "Meow" : { -> new Cat() },
    "Woof" : { -> new Dog() },
};

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.create();
}

Изменить 2019 год: В настоящее время это будет выглядеть примерно так.

import java.util.function.*;
import static java.util.Map.entry;

private static final Map<String,Supplier<Animal>> factoryMap = Map.of(
    "Meow", Cat::new, // Alternatively: () -> new Cat()
    "Woof", Dog::new // Note: No extra comma like arrays.
);

// For more than 10, use Map.ofEntries and Map.entry.
private static final Map<String,Supplier<Animal>> factoryMap2 = Map.ofEntries(
    entry("Meow", Cat::new),
    ...
    entry("Woof", Dog::new) // Note: No extra comma.
);

public Animal createAnimal(String action) {
    Supplier<Animal> factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.get();
}

Если вы хотите добавить параметр, вам нужно переключить Поставщик на Завод get станет apply , который тоже не имеет смысла в контексте). Для двух параметров BiFunction . Более двух параметров, и вы снова пытаетесь сделать его читабельным.

51
ответ дан 27 November 2019 в 03:15
поделиться

Используйте отсканированные изображения!

Шаг 1. Создайте аннотацию, как показано ниже:

package animal;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}

Обратите внимание, что RetentionPolicy - это среда выполнения, мы будем обращаться к ней через отражение.

Шаг 2. (Необязательно) Создайте общий суперкласс:

package animal;

public abstract class Animal {

    public abstract String greet();

}

Шаг 3. создайте подклассы с вашей новой аннотацией:

package animal;

@AniMake(action="Meow")
public class Cat extends Animal {

    @Override
    public String greet() {
        return "=^meow^=";
    }

}
////////////////////////////////////////////
package animal;

@AniMake(action="Woof")
public class Dog extends Animal {

    @Override
    public String greet() {
        return "*WOOF!*";
    }

}

Шаг 4. Создайте фабрику:

package animal;

import java.util.Set;

import org.reflections.Reflections;

public class AnimalFactory {

    public Animal createAnimal(String action) throws InstantiationException, IllegalAccessException {
        Animal animal = null;
        Reflections reflections = new Reflections("animal");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(AniMake.class);

        for (Class<?> clazz : annotated) {
            AniMake annoMake = clazz.getAnnotation(AniMake.class);
            if (action.equals(annoMake.action())) {
                animal = (Animal) clazz.newInstance();
            }
        }

        return animal;
    }

    /**
     * @param args
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        AnimalFactory factory = new AnimalFactory();
        Animal dog = factory.createAnimal("Woof");
        System.out.println(dog.greet());
        Animal cat = factory.createAnimal("Meow");
        System.out.println(cat.greet());
    }

}

] Эту фабрику можно немного очистить, например иметь дело с неприятными проверенными исключениями и т. д.
На этой фабрике я использовал библиотеку Reflections .
Я сделал это на собственном горьком опыте, т.е. я не делал проект maven, и мне пришлось добавлять зависимости вручную.
Зависимости:

Если вы пропустили шаг 2, вам нужно будет изменить фабричный метод, чтобы он возвращал Object.
С этого момента вы можете продолжать добавлять подклассы, и до тех пор, пока вы аннотируете их с помощью AniMake (или другого лучшего имени, которое вы придумали), и поместите их в пакет, определенный в конструкторе Reflections (в данном случае «животное»), и оставьте конструктор no-args по умолчанию видимым, тогда фабрика создаст для вас экземпляры ваших классов, не изменяя себя.

Вот результат:

log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=
8
ответ дан 27 November 2019 в 03:15
поделиться

Вы уже выбрали ответ на этот вопрос, но это все еще может помочь.

Хотя я разработчик .NET / C #, это действительно общая проблема ООП. Я столкнулся с той же проблемой и нашел хорошее решение (как мне кажется) с использованием контейнера IoC .

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

У меня был Factory, содержащий ссылку на контейнер IoC, который разрешается самим контейнером (в BootStrapper)

...
public AnimalFactory(IContainer container) 
{ 
    _container = container; 
}

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

в итоге ваш фабричный метод сокращается до следующего:

...
public Createable CreateAnimal(string action) 
{ 
    return _container.Resolve<Createable>(action); 
} 

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

Надеюсь, это поможет. Но в простых случаях это может оказаться излишним. Я использовал это, потому что у меня было 3 уровня зависимостей, которые нужно разрешить, и контейнер IoC, уже собирающий все мои компоненты.

2
ответ дан 27 November 2019 в 03:15
поделиться

В этом решении нет необходимости в картах. Карты - это просто другой способ выполнения оператора if / else. Воспользуйтесь небольшим размышлением, и всего несколько строк кода подойдут для всего.

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}

Вам нужно будет изменить аргументы с «Гав» и «Мяу» на «Кошка» и «Собака», но это должно быть достаточно легко. Это позволяет избежать любой «регистрации» Strings с именем класса на некоторой карте и делает ваш код повторно используемым для любого будущего Animal, которое вы можете добавить.

13
ответ дан 27 November 2019 в 03:15
поделиться

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

...
public enum Action {
    MEOW {
        @Override
        public Animal getAnimal() {
            return new Cat();
        }
    },

    WOOF {
        @Override
        public Animal getAnimal() {
            return new Dog();
        }
    };

    public abstract Animal getAnimal();
}

Тогда вы можете делать что-то вроде:

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...

Это немного круто, но это работает. Таким образом, компилятор будет ныть, если вы не определите getAnimal () для каждого действия, и вы не сможете передать действие, которого не существует.

12
ответ дан 27 November 2019 в 03:15
поделиться

Я не пробовал, но могу создать карту с «Мяу» и т. Д. В качестве ключей и (скажем) Cat.class как значение.

Обеспечьте создание статического экземпляра через интерфейс и вызовите его как

Animal classes.get("Meow").getInstance()
5
ответ дан 27 November 2019 в 03:15
поделиться

Я бы посмотрел, чтобы получить представление String в виде Enum и включить его.

4
ответ дан 27 November 2019 в 03:15
поделиться

Моя мысль заключается в том, чтобы как-то сопоставить строку String с функцией. Таким образом, вы можете передать Meow в map и вернуть конструктор функции, который уже был отображен. Я не уверен, как это сделать в Java, но быстрый поиск дал этот SO thread. Возможно, у кого-то есть идея получше.

1
ответ дан 27 November 2019 в 03:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: