2017-03-11 5 views
1

Ich habe ein kleines Dilemma mit Guice und Vermeidung von Nicht-Guice Singletons. Stellen Sie sich ein Projekt mit mehreren Modulen vor, in dem 3 Module vorhanden sind: shared, frontend und backend. Sowohl die frontend als auch die backend verwenden eine Instanz einer Profiling Klasse innerhalb des shared Moduls (welche Zeiten Methoden, und wird umfangreich während des Projekts verwendet).Verwendung der Dependency-Injektion (über Guice) in einer Multi-Modul-Umgebung

Fast jede einzelne Klasse erfordert die Verwendung dieser Profiling-Instanz (einschließlich User Objekte, die dynamisch erstellt werden, wenn ein Benutzer eine Verbindung herstellt).

Wenn jede einzelne Klasse eine Instanz der Profiling Klasse erfordert, gibt es Nachteile der verschiedenen Methoden, es zu tun:

Lösung 1 (im Konstruktor auf die Instanz Feld kopiert):

private final Profiling profiling; 

@Inject 
public User(Profiling profiling, String username) 

Nachteil: Sie müssen ein Profiling Objekt in jeden einzelnen Konstruktor einfügen. Dies ist umständlich und leicht sinnlos. Sie müssen auch Guices Injector statisch speichern (oder es injizieren), so dass Sie die User Objekte on-the-fly erstellen können und nicht nur, wenn das Programm zum ersten Mal geladen wird.

Lösung 2 (als Beispiel Feld nur):

@Inject 
private Profiling profiling; 

public User(String username) 

Drawback: Ähnlich wie oben, haben Sie Injector Guice zu gebrauchen jedes einzelne Objekt zu instanziiert. Dies bedeutet, dass zur dynamischen Erstellung von User Objekten eine Instanz des Injektors in der Klasse benötigt wird, die die Objekte User erzeugt.

Lösung 3 (als ein statisches Feld in einem (Haupt-) Klasse, manuell von uns erstellt)

public static final Profiling PROFILING; // Can also use a get method 

public Application() { 
    Application.PROFILING = injector.getInstance(Profiling.class) 
} 

Drawback: geht gegen Guice/der Abhängigkeitsinjektions Empfehlungen - Erstellen einer Singleton Profiling Objekt, das statisch zugegriffen wird (von Application.PROFILING.start()) besiegt den Zweck von Guice?

Lösung 4 (als statisches Feld in jeder einzelnen Klasse, injiziert durch Guice)

@Inject 
private static Profiling profiling; 

// You need to request every single class: 
// requestStaticInjection(XXXX.class) 

Nachteil: Auch dies geht gegen Guice/der Dependency Injection Empfehlungen, weil es statisch einspritzt. Ich muss auch jede einzelne Klasse anfordern, die Guice benötigt, um den Profiler zu injizieren (was auch mühsam ist).

Gibt es einen besseren Weg, um mein Projekt zu entwerfen und zu vermeiden, auf die Singleton-Entwurfsmuster zurückzugreifen, die ich verwendet habe?

TL; DR: Ich möchte auf diese Profiling-Instanz (eine pro Modul) über jede einzelne Klasse zugreifen können, ohne auf Singleton-Entwurfsmuster zurückzugreifen.

Danke!

+1

Ich würde es in jede Guice-Managed-Klasse injizieren, wie Sie vorgeschlagen (nicht statisch). Ich würde nur Constructor-Injection verwenden (weil das erzwingt, dass eine manuelle Konstruktion dieselben Argumente übergeben muss und keine ungültige Instanz erstellen kann). Der Benutzer klingt jedoch nicht wie eine von Hand verwaltete Klasse. Ich würde (vielleicht gibt es hier nicht genug Informationen) dies über eine User-Factory implementieren, die mit der Profiling-Klasse injiziert werden kann und das Problem, eine Profiling-Instanz in jedem Benutzer zu haben, abstrahiert. – pandaadb

Antwort

2

Pragmatisch würde ich ein normales Singleton verwenden, vielleicht durch ein Einzelfeld Enum oder ein ähnliches Muster.

Um zu sehen, warum, sollten Sie die Frage stellen: Was ist der Zweck von Guice, und der Abhängigkeit Injektion im Allgemeinen? Der Zweck besteht darin, die Teile Ihrer Anwendung so zu entkoppeln, dass sie unabhängig voneinander entwickelt und getestet sowie zentral konfiguriert und neu angeordnet werden können. In diesem Sinne müssen Sie die Kosten der Kopplung gegen die Kosten der Entkopplung wiegen. Dies hängt vom Objekt ab, das Sie koppeln oder entkoppeln möchten. Die Kosten für die Kopplung bestehen darin, dass Sie kein Teil Ihrer Anwendung ohne eine wirklich funktionierende Instanz von Profiling betreiben könnten, einschließlich in Tests, einschließlich für Modellobjekte wie Benutzer. Wenn Profiling daher Annahmen über seine Umgebung trifft, z. B. die Verfügbarkeit von System-Timing-Aufrufen mit hoher Auflösung, können Sie Klassen wie User nicht verwenden, ohne dass das Profiling deaktiviert werden kann. Wenn Sie möchten, dass Ihre Tests mit einer neuen (Nicht-Singleton) Profiling-Instanz aus Gründen der Testisolierung profiliert werden, müssen Sie diese separat implementieren. Wenn Ihre Profiling-Klasse jedoch so leicht ist, dass sie keine große Belastung darstellt, können Sie diesen Weg immer noch wählen.

Die Kosten der Entkopplung sind, dass jedes Objekt gezwungen werden kann, an injectable, as opposed to a newable zu werden. Sie wären dann in der Lage, neue/Dummy/Fake-Implementierungen von Profiling in Ihren Klassen und Tests zu ersetzen und für unterschiedliche Profiling-Verhalten in verschiedenen Containern zu konfigurieren, obwohl diese Flexibilität möglicherweise keine unmittelbaren Vorteile hat, wenn Sie keinen Grund haben, zu ersetzen diese Implementierungen. Für Klassen wie User, die später erstellt werden, müssten Sie Factory-Implementierungen verfolgen, wie sie beispielsweise unter Guice assisted injection oder AutoFactory code generation bereitgestellt werden. (Denken Sie daran, dass Sie eine beliebige Anzahl von Objekten erstellen, indem Provider<T> statt T für jedes Objekt Injektion würden Sie sonst injizieren, und dass eine Fabrik Beispiel Injektion wäre wie ein Provider Customizing get Parameter nehmen Sie wählen.)

Bezüglich Ihre Lösungen:

  • Lösungen 1 und 2, pro-Objekt-Injektion: Dies ist, wo eine Fabrik glänzen würde. (Ich würde die Konstruktorinjektion bevorzugen, wenn ich die Wahl hätte, also würde ich mit Lösung 1 zwischen ihnen gehen.) Natürlich müsste alles, was einen neuen Benutzer erzeugt, statt dessen ein User.Factory injizieren, so dass sich ein straffes Projekt ergibt in ein Projekt, um jede Klasse in Ihrer Codebasis in DI umzuwandeln - was für das, was Sie jetzt zu tun versuchen, ein inakzeptabler Preis sein kann.

    // Nested interface for your Factory: 
    public interface Factory { User get(String username); } 
    
    // Mark fields that the user provides: 
    @Inject public User(Profiling profiling, @Assisted String username) { ... } 
    
    // Wire up your Factory in a Module's configure: 
    install(new FactoryModuleBuilder().implement(User.Factory.class)); 
    
    // Now you can create new Users on the fly: 
    @Inject User.Factory userFactory; 
    User myNewUser = userFactory.get("timothy"); 
    
  • Lösung 3, statische Injektion eines Haupthalter anfordernden annähert, was ich im Sinn hatte: Für die Objekte nicht durch Dependency Injection geschaffen, statische Injektion für eine einzelne Klasse beantragen, wie ProfilingHolder, etc. zu jagen. Man könnte es sogar Verhalten no-op halber Flexibilität:

    public class ProfilingHolder { 
        // Populate with requestStaticInjection(ProfilingHolder.class). 
        @Inject static Profiling profilingInstance; 
        private ProfilingHolder() { /* static access only */ } 
        public static Profiling getInstance() { 
        if (profilingInstance == null) { 
         // Run without profiling in isolation and tests. 
         return new NoOpProfilingInstance(); 
        } 
        return profilingInstance; 
        } 
    } 
    

    Natürlich, wenn Sie auf Anrufe zu VM Singletons angewiesen sind, sind Sie wirklich eine normale VM-globale statische Singletonmuster umarmen, nur mit Zusammenspiel, um Guice wo möglich zu verwenden. Sie könnten dieses Muster einfach umdrehen und ein Guice-Modul bind(Profiling.class).toInstance(Profiling.INSTANCE); haben und den gleichen Effekt erhalten (vorausgesetzt, Profiling kann ohne Guice instanziiert werden).

  • Lösung 4, requestStaticInjection für jede einzelne Klasse ist die einzige, die ich nicht berücksichtigen würde. Die Liste der Klassen ist zu lang und die Wahrscheinlichkeit, dass sie variieren wird Profiling ist zu gering.Sie würden ein Modul in eine Einkaufsliste mit hohem Wartungsaufwand verwandeln und nicht irgendeine wertvolle Konfiguration, und Sie würden sich zwingen, die Kapselung zu durchbrechen oder Guice zum Testen zu verwenden.

So in der Zusammenfassung, würde ich Guice Singletons Injektion für Ihre aktuelle injizierbare Objekte, normal Singletons für Ihre aktuelle newable Objekte und die Möglichkeit der Migration auf Fabriken, wenn/falls eines Ihrer wählen newables machen Sie den Sprung zu Injektionen.