Reputation:
Assume there's a complex application where we store and retrieve a set of application settings.
Application settings used into many application classes and there are two ways to solve this task.
The first one. Declare ApplicationSettings as singleton.
public class ApplicationSettings {
private static final ApplicationSettings instance = new ApplicationSettings();
public static ApplicationSettings getInstance() {
return instance;
}
// ... public methods to set and retrieve settings information, save settings, etc.
private ApplicationSettings() {
// loading the application settings in the private constructor
}
}
And we have an Application
class (and many other classes), where we use application settings.
public class Application {
public Application() {
// .... initialization code
}
public void doSomething() {
ApplicationSettings applicationSettings = ApplicationSettings.getInstance();
someMethod1(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY1));
someMethod2(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY2));
// etc
}
}
It may look convenient, but use of singletons has some serious drawbacks, for instance, singleton classes are hard to test. And use of singleton classes makes classes less flexible and manageable.
As an option, to avoid use of singletons, we declare ApplicationSettings
as a regular class, not singleton. And pass ApplicationSettings
instance as a parameter to constructors of classes which are using ApplicationSettings
for internal activities.
The second way:
public interface ApplicationSettingsInterface {
// ... public methods to set and retrieve settings information, save settings, etc.
}
public class ApplicationSettings implements ApplicationSettingsInterface {
private ApplicationSettings() {
// loading the application settings in the private constructor
}
// ... public methods to set and retrieve settings information, save settings, etc.
}
public class Application {
private final ApplicationSettingsInterface applicationSettings;
public Application(ApplicationSettingsInterface applicationSettings) {
this.applicationSettings = applicationSettings;
// .... initialization code
}
public void doSomething() {
someMethod1(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY1));
someMethod2(applicationSettings.getSetting(ApplicationSettings.SETTING_SOME_KEY2));
// etc
}
}
I believe that the second way is more flexible and produces more manageable code than the first way with the singleton.
My question: the second way assumes that we need to store reference to application settings into every class instance that uses app. settings and gets app. settings object as a constructor parameter.
Is it ok to have those application settings references stored into many object instances or there's a better, third way to deal with this task?
Thank you.
Upvotes: 5
Views: 1535
Reputation: 2011
Typically when talking about ApplicationSettings
, we talk about settings that are used in a leaf project in the project dependency hierarchy that will end up as an executable. At this level a DI container can help with setting up the configuration beans together with some ApplicationSettings
instances.
On the other hand, library projects and their classes usually define their own settings classes or use Map<String, String>
or java.util.Properties
directly. They are free of DI container annotations and therefore require you to pass an instance of the correct settings.
I've seen the following approaches in libraries to handle settings:
It seems natural to group settings together in a settings objects, but the majority of library classes do simply work with a lot of setters to alter specific behavior. This implies that you find reasonable default values for each of those settings. If the class needs then too many setters, it's also an indication that the class has too many responsibilities.
You may consider creating a specific settings object for your class with values that are only used by the corresponding class, e.g. a Database
class requires DatabaseSettings
. You can further separate the dependency between your class and the settings class by providing a factory or factory method, e.g. like Database.from(DatabaseSettings)
so that your DatabaseImpl
doesn't even have a dependency to the settings object itself. The factory can then even use a builder to create the complex object that requires a lot of configurations by passing the settings from the settings object to the builder methods before returning the object. Having smaller settings objects for certain use cases allows you to reuse them in other settings. Imagine your application wants to copy data from one database to another, thus requires the definition of two DatabaseSettings
, one for source and one for the target Database
. Instead of placing them in one, the ApplicationSettings
composes two fields holding two DatabaseSettings
. If your connection requires additional SSLSettings
to create a SSLContext
or a SSLSocketFactory
, you may simply add a SSLSettings
field to your DatabaseSettings
and all your Database
factories do now support SSL. These kind of nested or hierarchical settings are very flexible.
You may also have a look at the ServiceLocator
pattern which allows you to register and access any kind of service, thus also your settings or some SettingsService
. The pattern has its own drawbacks and is discussed intensively in other posts. It hides the dependencies, but if designed right, it can likewise be easily used like a DI container.
The static access to the ApplicationSettings
can be directly passed to a second (package-private) constructor that can be used within tests to pass a different settings object for testing.
Finally you can also use a static setter method in addition to your static ApplicationSettings.get()
to exchange your settings object during testing. There are quite some classes in the jdk designed like this, e.g. SSLContext.setDefault(..)
. It's very simple and convenient, but take care to avoid overriding the settings in different tests when running them in parallel.
Final thoughts about singletons: we have some legacy code that uses them and they are the worst choice in my opinion and we try to refactor them as good as possible, because:
Upvotes: 1
Reputation:
The first way (with singleton/factory) has serious drawbacks. Singletons are hard to test and use of singleton(s) induces tight coupling to your system. When components are tight coupled then it is hard to combine different components with each other, it significantly reduces the flexibility of the whole product.
The second way, when we pass ApplicationSettings
as a parameter to the constructor, has it's own drawback that comes to the stage when we have complex inheritance tree.
Imagine we have classes: GrandParentOfSomeObject
-> SomeObject
-> ChildSomeObject
And SomeObject
class does not need ApplicationSettings
at all. But ChildSomeObject
requries access to application settings.
In this case we need to pass ApplicationSettings
object reference to SomeObject
constructor, even SomeObject
does not require this object, it is necessary to pass it. To provide it later to ChildSomeObject
.
This approach pollutes the code with unnecessary functionality and makes it less elegant and flexible.
The third, preferred way, is use of dependency injection approach, to inject application settings object whenever it is required.
I use Google Guice to demonstrate this way.
Below, there's code with my comments.
SettingsInterface.java
- interface to represent application settings functionality.
package com.mycompany.settingstest;
public interface SettingsInterface {
int getFirstSetting();
void setFirstSetting(int value);
String getSecondSetting();
void setSecondSetting(String value);
boolean getThirdSetting();
void setThirdSetting(boolean value);
}
ApplicationSettings.java
- concrete implementation of SettingsInterface
class.
package com.mycompany.settingstest;
import javax.inject.Singleton;
@Singleton
public class ApplicationSettings implements SettingsInterface{
private int firstSetting = -1234567890;
private String secondSetting = "some default string value";
private boolean thirdSetting = true;
public ApplicationSettings() {
}
@Override
public int getFirstSetting() {
return this.firstSetting;
}
@Override
public void setFirstSetting(int value) {
this.firstSetting = value;
}
@Override
public String getSecondSetting() {
return this.secondSetting;
}
@Override
public void setSecondSetting(String value) {
this.secondSetting = value;
}
@Override
public boolean getThirdSetting() {
return this.thirdSetting;
}
@Override
public void setThirdSetting(boolean value) {
this.thirdSetting = value;
}
}
ApplicationSettingsModule.java
- module to declare necessary bindings, which can be used by Google Guice upon dependency injection.
package com.mycompany.settingstest;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
public class ApplicationSettingsModule extends AbstractModule {
@Override
protected void configure() {
//bind the service to implementation class
bind(SettingsInterface.class).to(ApplicationSettings.class).in(Scopes.SINGLETON);//this is lazy singleton
//bind(SettingsInterface.class).to(ApplicationSettings.class).asEagerSingleton();//this is eager one
}
}
MainApplication.java
- the main application class, also there is an application component class declared to demonstrate the functionality of application settings.
package com.mycompany.settingstest;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
public class MainApplication {
public static void main(String[] args) {
Injector applicationSettingsInjector = Guice.createInjector(new ApplicationSettingsModule());
SettingsInterface applicationSettings = applicationSettingsInjector.
getInstance(SettingsInterface.class);
System.out.println(" ******* Main Application *******");
System.out.println(" Initial setting values ");
System.out.println("first setting: " + applicationSettings.getFirstSetting());
System.out.println("second setting: " + applicationSettings.getSecondSetting());
System.out.println("third setting: " + applicationSettings.getThirdSetting());
System.out.println(" ******* ******* *******");
System.out.println(" ******* We're changing settings *******");
applicationSettings.setFirstSetting(789);
applicationSettings.setSecondSetting("another custom string");
applicationSettings.setThirdSetting(false);
System.out.println(" Settings were changed. Check changes below: ");
System.out.println("first setting: " + applicationSettings.getFirstSetting());
System.out.println("second setting: " + applicationSettings.getSecondSetting());
System.out.println("third setting: " + applicationSettings.getThirdSetting());
System.out.println(" ******* ******* *******");
System.out.println(" Now composing the application component and injecting application settings to it");
ApplicationComponent applicationComponent = applicationSettingsInjector.getInstance(ApplicationComponent.class);
applicationComponent.execute();
}
}
class ApplicationComponent {
@Inject
private SettingsInterface applicationSettings;
public ApplicationComponent() {
}
public void execute() {
System.out.println(" ******* Application component *******");
System.out.println("Application settings instance is already injected");
System.out.println("first setting: " + applicationSettings.getFirstSetting());
System.out.println("second setting: " + applicationSettings.getSecondSetting());
System.out.println("third setting: " + applicationSettings.getThirdSetting());
System.out.println(" ******* ******* *******");
}
}
Please note that in ApplicationComponent
class we declare:
@Inject
private SettingsInterface applicationSettings;
and in the execute()
method we have already constructed ApplicationSettings
object and can use it, and we don't pass this object reference to the ApplicationComponent
constructor.
Later, we can decide to use another implementation of SettingsInterface
instead of ApplicationSettings
. For example, in the first version we stored application settings into text file. And in the next version of our product we would want to use some complex centralized settings provider mechanism.
And in this case we would not need to change our code globally. We just change bindings in the ApplicationSettingsModule
to bind another implementation to the SettingsInterface
and it will be enough to provide new application settings mechanism to all components of our system.
update
forgot to mention pom.xml
contents and the output.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>SettingsTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
</project>
output
--- exec-maven-plugin:1.2.1:exec (default-cli) @ SettingsTest ---
******* Main Application *******
Initial setting values
first setting: -1234567890
second setting: some default string value
third setting: true
******* ******* *******
******* We're changing settings *******
Settings were changed. Check changes below:
first setting: 789
second setting: another custom string
third setting: false
******* ******* *******
Now composing the application component and injecting application settings to it
******* Application component *******
Application settings instance is already injected
first setting: 789
second setting: another custom string
third setting: false
******* ******* *******
------------------------------------------------------------------------
Upvotes: 4
Reputation: 1151
You may also implement ApplicationSettingsFactory
defining method ApplicationSettingsInterface Create()
and use it as dependency. Thus, class will be more testable.
Factory can incorporate object creation policy (singletone or not).
And finally, why don't you use IoC container to define singletone or not? IoC containers are factories itself, and if you are not interested in creation of configuration object in arbitrary places in code, I suggest dependency injection of `ApplicationSettingsInterface' to be best solution. Otherwise, you may use your own factory.
Upvotes: 1
Reputation: 2672
I believe the second idea is better, as it reveals dependences through interfaces. Other approach: if you plan application settings to be globally reachable in your code, you can make them static. Such a class would be accessible from everywhere.
Using a Singleton is probably a bad idea, as it usually hides connections between modules instead of revealing them through interfaces.
While making the static class globally reachable may look similar to singleton (as it does not need to explicitly show dependencies through interfaces) I consider it to be still better than a singleton because if you have a globally reachable static class you can expect it to be globally used (it becomes a container, or a method-toolbox, which probably does not need to be changed often.)
Upvotes: 1