Pergunta

Eu tenho algumas classes, como mostrado aqui

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
    }
}

Como posso alterar TrueFalseQuestion classe, de modo a que o método estático é executado assim que eu chegar em 1 em vez de 0, quando eu executar a minha principal método?Eu não quero qualquer alteração no método main.

Eu realmente estou tentando implementar os padrões de fábrica, onde as subclasses de registrar com a fábrica, mas eu simplifiquei o código para esta pergunta.

Foi útil?

Solução

Para registrar o TrueFalseQuestion Classe com a fábrica, seu inicializador estático precisa ser chamado. Para executar o inicializador estático do TrueFalseQuestion classe, a classe precisa ser referenciada ou precisa ser carregada por reflexão antes QuestionFactory.map.size() é chamado. Se você quiser deixar o main Método intocado, você deve fazer referenciá -lo ou carregá -lo por reflexão no QuestionFactory Inicializador estático. Eu não acho que seja uma boa ideia, mas vou apenas responder sua pergunta :) se você não se importa com o QuestionFactory Saber sobre todas as classes que implementam Question Para construí -los, você pode apenas referenciá -los diretamente ou carregá -los através da reflexão. Algo como:

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);
     }
 }

Certificar-se de que mapA declaração e construção estão antes do static quadra. Se você não quiser QuestionFactory ter qualquer conhecimento das implementações de Question, você terá que listá -los em um arquivo de configuração que é carregado por QuestionFactory. A única outra maneira (possivelmente insana) que eu conseguia pensar em fazê -lo seria examinar todo o caminho de classe para as aulas que implementam Question :) Isso pode funcionar melhor se todas as classes que implementaram Question foram obrigados a pertencer ao mesmo pacote - Nota: não estou endossando esta solução;)

A razão pela qual eu não acho que fazer nada disso no QuestionFactory Inicializador estático é porque classes como TrueFalseQuestion ter seu próprio inicializador estático que chama para QuestionFactory, que nesse ponto é um objeto incompletamente construído, que está apenas pedindo problemas. Ter um arquivo de configuração que simplesmente lista as classes que você deseja QuestionFactory Para saber como construir, registrá -los em seu construtor é uma boa solução, mas isso significaria mudar seu main método.

Outras dicas

Você pode ligar:

Class.forName("yourpackage.TrueFalseQuestion");

Isso carregará a classe sem você tocá -la e executará o bloco inicializador estático.

O inicializador estático da classe não pode ser executado se a classe nunca for carregada.

Portanto, você precisa carregar todas as classes corretas (o que será difícil, pois você não as conhece em tempo de compilação) ou se livrar do requisito para o inicializador estático.

Uma maneira de fazer o último é usar o ServiceLoader.

Com o ServiceLoader você simplesmente coloca um arquivo em META-INF/services/package.Question e liste todas as implementações. Você pode ter múltiplo Esses arquivos, um por arquivo .jar. Dessa forma, você pode enviar facilmente adicional Question implementações separadas do seu programa principal.

No QuestionFactory Você pode simplesmente usar ServiceLodaer.load(Question.class) para obter um ServiceLoader, que implementa Iterable<Question> e pode ser usado assim:

for (Question q : ServiceLoader.load(Question.class)) {
    System.out.println(q);
}

Para executar o inicializadores estáticos, as aulas precisam ser carregados.Para que isso aconteça, o "main" da classe deve depender (directa ou indirectamente) sobre as classes, ou deve, direta ou indiretamente, causar-lhes para ser carregado dinamicamente;exemplo:usando Class.forName(...).

Eu acho que você está tentando evitar dependências embutido em seu código-fonte.De modo estático dependências são inaceitáveis, e chamadas para Class.forName(...) com hard-coded nomes de classe são inaceitáveis bem.

Isso deixa duas alternativas:

  • Escreva alguns bagunçado código para iterar sobre os nomes de recursos em algum pacote e, em seguida, usar Class.forName(...) para carregar os recursos que olhar como as suas classes.Esta abordagem é complicado se você tiver um complicado classpath, e impossível se o seu eficaz classpath inclui um URLClassLoader com uma URL remota (por exemplo).

  • Criar um arquivo (por exemplo,um carregador de classe de recurso) que contém uma lista de nomes de classes que você deseja carregar, e escrever um simples código para ler o arquivo e usar Class.forName(...) a carga de cada um.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top