У меня есть несколько классов как показано здесь
public class TrueFalseQuestion implements Question{
static{
QuestionFactory.registerType("TrueFalse", "Question");
}
public TrueFalseQuestion(){}
}
...
public class QuestionFactory {
static final HashMap<String, String > map = new HashMap<String,String>();
public static void registerType(String questionName, String ques ) {
map.put(questionName, ques);
}
}
public class FactoryTester {
public static void main(String[] args) {
System.out.println(QuestionFactory.map.size());
// This prints 0. I want it to print 1
}
}
Как я могу измениться TrueFalseQuestion
класс так, чтобы статический метод всегда выполнялся так, чтобы я добрался 1 вместо 0, когда я выполняю свой основной метод? Я не хочу изменения в основном методе.
Я на самом деле пытаюсь реализовать шаблоны "фабрика", где подклассы регистрируются в фабрике, но я упростил код для этого вопроса.
Чтобы зарегистрировать класс TrueFalseQuestion
в фабрике, необходимо вызвать его статический инициализатор.Чтобы выполнить статический инициализатор класса TrueFalseQuestion
, на класс необходимо либо ссылаться, либо он должен быть загружен путем отражения до вызова QuestionFactory.map.size ()
. Если вы хотите оставить метод main
нетронутым, вы должны ссылаться на него или загрузить его путем отражения в статическом инициализаторе QuestionFactory
. Я не думаю, что это хорошая идея, но я просто отвечу на ваш вопрос :) Если вы не возражаете, QuestionFactory
знает обо всех классах, которые реализуют Question
для построения их, вы можете просто сослаться на них напрямую или загрузить их через отражение. Примерно так:
public class QuestionFactory {
static final HashMap<String, String > map = new HashMap<String,String>();
static {
this.getClassLoader().loadClass("TrueFalseQuestion");
this.getClassLoader().loadClass("AnotherTypeOfQuestion"); // etc.
}
public static void registerType(String questionName, String ques ) {
map.put(questionName, ques);
}
}
Убедитесь, что объявление и конструкция map
находятся перед блоком static
. Если вы не хотите, чтобы QuestionFactory
знал о реализациях Question
, вам нужно будет указать их в файле конфигурации, который загружается с помощью QuestionFactory
].Единственный другой (возможно, безумный) способ, который я мог придумать, - это просмотреть весь путь к классам в поисках классов, реализующих Question
:) Это могло бы работать лучше, если бы все классы, реализующие Question
должны принадлежать к одному пакету - ПРИМЕЧАНИЕ: я не поддерживаю это решение;)
Я не думаю, что делать что-либо из этого в статическом инициализаторе QuestionFactory
потому, что классы например, TrueFalseQuestion
имеют свой собственный статический инициализатор, который вызывает QuestionFactory
, который на тот момент является не полностью сконструированным объектом, который просто вызывает проблемы. Имея файл конфигурации, в котором просто перечисляются классы, которые вы хотите QuestionFactory
уметь создавать, а затем регистрировать их в его конструкторе, это прекрасное решение, но это будет означать изменение вашего метода main
.
Вы можете вызвать:
Class.forName("yourpackage.TrueFalseQuestion");
, это загрузит класс, не касаясь его, и выполнит блок статического инициализатора.
Статический инициализатор класса не может быть выполнен, если класс никогда не загружается.
Таким образом, вам нужно либо загрузить все правильные классы (что будет сложно, поскольку вы не знаете их все во время компиляции), либо избавитесь от требования статического инициализатора.
Один из способов сделать последнее - использовать ServiceLoader
.
С помощью ServiceLoader
вы просто помещаете файл в META-INF / services / package.Question
и перечисляете все реализации. У вас может быть несколько таких файлов, по одному на файл .jar. Таким образом, вы можете легко поставлять дополнительные реализации Question
отдельно от вашей основной программы.
В QuestionFactory
вы можете просто использовать ServiceLodaer.load (Question.class)
, чтобы получить ServiceLoader
, который реализует Iterable
и может использоваться следующим образом:
for (Question q : ServiceLoader.load(Question.class)) {
System.out.println(q);
}
Для запуска статических инициализаторов необходимо загрузить классы. Для этого либо ваш «основной» класс должен зависеть (прямо или косвенно) от классов, либо он должен прямо или косвенно вызывать их динамическую загрузку; например используя Class.forName (...)
.
Я думаю, вы пытаетесь избежать зависимостей , встроенных в ваш исходный код . Таким образом, статические зависимости недопустимы, и вызовы Class.forName (...)
с жестко заданными именами классов также недопустимы.
Это оставляет вам две альтернативы:
Напишите какой-нибудь беспорядочный код для перебора имен ресурсов в каком-нибудь пакете, а затем используйте Class.forName (...)
для загрузки тех ресурсов, которые выглядят как ваши классы.Этот подход сложен, если у вас сложный путь к классам, и невозможен, если ваш эффективный путь к классам включает URLClassLoader с удаленным URL-адресом (например).
Создайте файл (например, ресурс загрузчика классов), содержащий список имен классов, которые вы хотите загрузить, и напишите простой код для чтения файла и используйте Class.forName (...)
для загрузки каждый.