Frage

Ich habe ein paar Klassen, wie hier gezeigt

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

Wie kann ich TrueFalseQuestion Klasse ändern, so dass die statische Methode wird immer so läuft, dass ich 1 statt 0, wenn ich meine Hauptmethode laufen? Ich will keine Änderung im Hauptverfahren.

Ich versuche tatsächlich die Fabrik Muster zu implementieren, wo die Unterklassen mit der Fabrik zu registrieren, aber ich habe den Code für diese Frage vereinfacht.

War es hilfreich?

Lösung

Um die TrueFalseQuestion Klasse mit dem Werk zu registrieren, seine statische Initialisierer Bedürfnisse aufgerufen werden. Um die statische auszuführen initializer der TrueFalseQuestion Klasse, um die Klasse Bedürfnisse entweder referenziert werden oder es muss durch Reflexion geladen werden, bevor QuestionFactory.map.size() genannt wird. Wenn Sie die main Methode unangetastet lassen wollen, müssen Sie es in den QuestionFactory statischen Initialisierer durch Reflexion Referenz oder zu laden. Ich glaube nicht, das ist eine gute Idee, aber ich werde einfach Ihre Frage beantworten :) Wenn Sie nicht die QuestionFactory nichts dagegen über alle Klassen zu wissen, dass Question implementieren, sie zu konstruieren, können Sie sie einfach direkt referenzieren oder laden Sie sie durch Betrachtung. So etwas wie:

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

Stellen Sie sicher, map Erklärung und Konstruktion ist vor dem static Block. Wenn Sie nicht QuestionFactory keine Kenntnis von den Implementierungen von Question haben wollen, müssen Sie sie in einer Konfigurationsdatei aufzulisten, die von QuestionFactory geladen wird. Die einzige andere (möglicherweise verrückt), wie ich denken konnte, es zu tun, wäre für die Klassen durch die gesamte Classpath zu suchen, die Question :) Das könnte funktionieren besser, wenn alle Klassen implementieren, die umgesetzt Question zu demselben Paket gehören erforderlich waren - Hinweis: ich bin nicht diese Lösung befürwortet;)

Der Grund, warum ich glaube nicht, dass dies in der QuestionFactory statischen tun Initializer ist, weil Klassen wie TrueFalseQuestion ihre eigene statische haben initializer, dass Anrufe in QuestionFactory, die an diesem Punkt ist ein unvollständig konstruiertes Objekt, das nur Ärger bittet . eine Konfigurationsdatei hat, die einfach die Klassen Listen, die Sie QuestionFactory wissen wollen, wie dann zu konstruieren, so dass sie in seinem Konstruktor Registrierung ist eine feine Lösung, aber es würde bedeuten, dass Ihre main Methode zu ändern.

Andere Tipps

Sie können nennen:

Class.forName("yourpackage.TrueFalseQuestion");

Dies wird die Klasse laden, ohne dass Sie es tatsächlich zu berühren, und wird die statischen Initialisierungsblocks auszuführen.

Die statischen Initialisierer für die Klasse kann nicht ausgeführt werden, wenn die Klasse nie geladen wird.

So müssen Sie entweder alle die richtigen Klassen laden (die schwer sein werden, da Sie sie nicht wissen alle zum Zeitpunkt der Kompilierung) oder die Anforderung loszuwerden für die statischen Initialisierer.

Eine Möglichkeit, diese zu tun ist, die ServiceLoader .

Mit dem ServiceLoader Sie einfach eine Datei in META-INF/services/package.Question setzen und alle Implementierungen aufzulisten. Sie können haben mehrere solche Dateien, eine pro .jar-Datei. Auf diese Weise können leicht zusätzliche Question Implementierungen getrennt von Ihrem Hauptprogramm versenden können.

In der QuestionFactory können Sie dann einfach ServiceLodaer.load(Question.class) verwenden, um eine ServiceLoader zu erhalten, die Geräte Iterable<Question> und können wie folgt verwendet werden:

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

Um die statischen Initialisierungen auszuführen, müssen die Klassen geladen werden. Damit dies geschehen kann, entweder die „main“ Klasse muss (direkt oder indirekt) von den Klassen ab, oder er muss sie direkt oder indirekt verursacht dynamisch geladen werden; z.B. mit Class.forName(...).

Ich glaube, Sie versuchen, um Abhängigkeiten zu vermeiden eingebettet in Ihrem Quellcode . So statische Abhängigkeiten sind nicht akzeptabel, und Anrufe Class.forName(...) mit hartcodierte Klassennamen sind nicht akzeptabel als auch.

Dies lässt Sie zwei Alternativen:

  • Schreiben Sie etwas chaotisch Code zu iterieren den Ressourcennamen in einem Paket, und dann Class.forName(...) verwenden, um diese Ressourcen zu laden, die aussehen wie Ihre Klassen. Dieser Ansatz ist schwierig, wenn Sie eine komplizierte Classpath haben, und unmöglich, wenn Ihr effektiven Classpath eine URLClassLoader mit einer Remote-URL enthält (zum Beispiel).

  • Erstellen Sie eine Datei (zB ein Classloader Ressource) eine Liste der Klassennamen enthält, dass Sie geladen werden soll, und schreiben einige einfache Code die Datei und Verwendung Class.forName(...) lesen jeden zu laden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top