2010-03-23 12 views
30

Ich bin oft mit dem Problem konfrontiert, einige (möglicherweise komplexe) Konfigurationseinstellungen, die aus Dateien im Dateisystem geladen wurden, im Speicher abzulegen. Ich frage mich, ob es eine bessere Möglichkeit gibt, ein Muster für dieses Problem zu entwerfen, als das, was ich verwendet habe.Design Pattern für einmalig geladene Konfigurationseigenschaften?

Im Wesentlichen umfasst meine aktuelle Lösung drei Schritte.

  1. Erstellen Sie ein Singleton. Da Daten persistent sind und garantiert, dass sie sich während der Laufzeit der Anwendung nicht ändern, sollte immer nur eine Objektinstanz benötigt werden.

  2. Wenn die erste Anfrage für das Objekt gemacht wird, erstellen Sie das Objekt und lesen Sie aus einer Datei ein.

  3. Daten mit Gettern freilegen.

Dies hat den Effekt, dass viele meiner Code sieht wie folgt aus: MyConfiguration.getInstance().getWeightOfBomb(), die mir ziemlich seltsam aussieht.

Gibt es einen besseren Weg, dies in einer semantischen Weise zu behandeln?

Antwort

29

Dependency Injection. Sie müssen nicht unbedingt ein DI-Framework wie Spring oder Guice verwenden, aber Sie wollen wirklich vermeiden, dass Ihr Code mit Singletons verunreinigt wird. Sie können weiterhin einen Singleton in der Implementierung verwenden, aber es gibt keinen Grund, dass der Rest Ihres Codes wissen muss, dass es sich um einen Singleton handelt. Singletons sind große Schmerzen beim Komponententest und Refactoring. Lassen Sie Ihren Code stattdessen auf eine Schnittstelle verweisen. z.B.

interface MyConfig { 
    double getWeightOfBomb(); 
} 

class SomeClass { 
    private MyConfig myConfig; 

    public void doSomething() { 
     myConfig.getWeightOfBomb(); 
    } 
} 

class MyConfigImpl implements MyConfig { 
    public double getWeightOfBomb() {   
      return MyConfiguration.getInstance().getWeightOfBomb(); 
    } 
} 

Wenn Sie einen DI Framework verwenden, nur Setup Sie Klassen Ihre MyConfig Implementierung injiziert haben. Wenn Sie dies nicht tun, dann ist der faulste Ansatz, der noch alle Vorteile hat, ist zu tun, so etwas wie:

class SomeClass { 
    private MyConfig myConfig = new MyConfigImpl();   
} 

Wirklich ist es an Ihnen. Wichtig ist, dass Sie myConfig pro Instanz ersetzen können, wenn Sie später feststellen, dass Sie das Verhalten variieren und/oder für Unit-Tests benötigen.

+3

Viele Manchmal muss ich einige Konfigurationseigenschaften in kleine Konsolen-Apps umwandeln (die ich in letzter Zeit massenweise geschrieben habe) . In größeren Anwendungen (für die ich dieses Muster übernommen habe) funktioniert diese Methode jedoch gut. –

2

Zusätzlicher Vorschlag für Noahs Antwort.

Wenn es für Sie unbequem ist, eine Methode für jeden Konfigurationsparameter zu schreiben, können Sie enum dafür verwenden. Hier ist, was ich meine:

public class Configuration { 

private final Properties properties; 

public enum Parameter { 
    MY_PARAMETER1("my.parameter1", "value1"), 
    MY_PARAMETER2("my.parameter2", "value2"); 

    private final String name; 
    private final String defaultValue; 

    private Parameter(String name, String defaultValue) { 
     this.name = name; 
     this.defaultValue = defaultValue; 
    } 

    private String getName() { 
     return name; 
    } 

    private String getDefaultValue() { 
     return defaultValue; 
    } 
} 


public Configuration(Properties properties) { 
    this.properties = (Properties)properties.clone(); 
} 

//single method for every configuration parameter 
public String get(Parameter param) { 
    return properties.getProperty(param.getName(), param.getDefaultValue()); 
} 

}

Danach, wenn Sie einen neuen Konfigurationsparameter haben, alles, was Sie tun müssen, um einen neuen ENUM-Eintrag hinzuzufügen.

Sie können natürlich auch eine Schnittstelle aus der Configuration-Klasse extrahieren, indem Sie enum nach außen verschieben.

3

Sie eine Schnittstelle erstellen könnte die Konfiguration repräsentieren:

public interface Config { 
    interface Key {} 
    String get(Key key); 
    String get(Key key, String defaultValue); 
} 

Und eine Singleton-Implementierung:

public enum MyConfig implements Config { 
    INSTANCE("/config.properties"); 
    private final Properties config; 

    MyConfig(String path) { 
     config = new Properties(); 
     try { 
      config.load(this.getClass().getResourceAsStream(path)); 
     } catch (IOException | NullPointerException e) { 
      throw new ExceptionInInitializerError(e); 
     } 
    } 

    @Override 
    public String get(Config.Key key){ 
     return config.getProperty(key.toString()); 
    } 

    @Override 
    public String get(Config.Key key, String defaultValue) { 
     return config.getProperty(key.toString(), defaultValue); 
    } 

    public enum Key implements Config.Key { 
     PROXY_HOST("proxy.host"), 
     PROXY_PORT("proxy.port"); 
     private final String name; 

     Key(String name) { this.name = name; }  
     @Override 
     public String toString() { return name; } 
    } 
} 

Und dann die Konfiguration in Ihren Klassen injizieren:

public class SomeClass { 
    private final Config config; 

    public SomeClass(Config config) { 
     this.config = config; 
    } 

    public void someMethod() { 
     String host = config.get(Key.PROXY_HOST); 
     String port = config.get(Key.PROXY_PORT, "8080"); 
     // Do something 
    } 
}