В настоящее время у меня есть метод, который действует как фабрика на основе данной Строки. Например:
public Animal createAnimal(String action)
{
if (action.equals("Meow"))
{
return new Cat();
}
else if (action.equals("Woof"))
{
return new Dog();
}
...
etc.
}
То, что я хочу сделать, избегают всего, если еще проблема, когда список классов растет. Я полагаю, что у меня должно быть два метода, тот, который регистрирует Строки к классам и другому, который возвращает класс на основе Строки действия.
Что хороший путь состоит в том, чтобы сделать это в Java?
. То, что вы сделали, вероятно, лучший способ сделать это, пока не станет доступна строка включения. ( 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
. Более двух параметров, и вы снова пытаетесь сделать его читабельным.
Используйте отсканированные изображения!
Шаг 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^=
Вы уже выбрали ответ на этот вопрос, но это все еще может помочь.
Хотя я разработчик .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, уже собирающий все мои компоненты.
В этом решении нет необходимости в картах. Карты - это просто другой способ выполнения оператора if / else. Воспользуйтесь небольшим размышлением, и всего несколько строк кода подойдут для всего.
public static Animal createAnimal(String action)
{
Animal a = (Animal)Class.forName(action).newInstance();
return a;
}
Вам нужно будет изменить аргументы с «Гав» и «Мяу» на «Кошка» и «Собака», но это должно быть достаточно легко. Это позволяет избежать любой «регистрации» Strings с именем класса на некоторой карте и делает ваш код повторно используемым для любого будущего Animal, которое вы можете добавить.
Если у вас нет для использования строк, вы можете использовать тип перечисления для действий и определить абстрактный фабричный метод.
...
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 () для каждого действия, и вы не сможете передать действие, которого не существует.
Я не пробовал, но могу создать карту
с «Мяу» и т. Д. В качестве ключей
и (скажем) Cat.class
как значение.
Обеспечьте создание статического экземпляра через интерфейс и вызовите его как
Animal classes.get("Meow").getInstance()
Я бы посмотрел, чтобы получить представление String в виде Enum и включить его.
Моя мысль заключается в том, чтобы как-то сопоставить строку String с функцией. Таким образом, вы можете передать Meow
в map и вернуть конструктор функции, который уже был отображен. Я не уверен, как это сделать в Java, но быстрый поиск дал этот SO thread. Возможно, у кого-то есть идея получше.