This was a design I use in cases where configuration should be set by the client (person using your code) for a framework.
The idea is to allow the client to specify the configuration, but prevent the client from storing an instance of the Config so it can be modified later on; a truely immutable config.
This is a design I came across myself through experimenting. It started by simply passing the client a Config object, which is used to specify type-safe configurations as well as guide the client by showing them their options (which properties should/can be set) through templates. This means when you put a period after config, it'll give you all the available methods you can choose from, leaving out any irrelevant methods.
public final class Config {
//details needed by the framework, but not by the client
Config() { }
//declare setters so the client can specify
//declare getters so the framework can access
}
public abstract class Framework {
public final void start() {
//check if running
Config config = new Config();
init(config);
//use config
}
protected abstract void init(Config config);
}
public final class MyApp extends Framework {
protected void init(Config config) {
//set config through setter methods
}
}
The idea was to hide the ability of creating a Config from the client. The framework would pass in the config, ensuring the client couldn't create "garbage" config objects.
The problem with this is that MyApp can store the config object in a field, allowing the client to mutate it (change it's state) later. Our Config is not immutable, because we NEED to set the values in a place other than the constructor. We could implement validations in the config's setter methods, to ensure each field is only set once, but that would be quite a bit to manage.
To fix this, I implemented a Builder for Config. The Builder pattern allows the developer to specify the properties of an object, then build it afterwards, allowing immutability after building. To do this, we pass the responsibility of creating the Config class to it's builder. The developer uses the builder to specify the properties (which are stored in the builder). Once you decide to build() the object, the Builder passes itself to the constructor of the object we want, initializing the fields with what we specified in the builder:
public final class Config {
//private final fields
private Config(Builder builder) {
//use builder to init final fields
}
public static final class Builder {
//config properties; will be transfered to Config
public Builder setProperty(...) {
//assign to field
return this;
}
public Config build() {
return new Config(this);
}
}
}
Instead of passing a Config object to the client, we would pass a Config.Builder object. This will allow them to set the properties of the config, while ensuring the properties are immutable. There's a downside to this: Framework doesn't have any access to the config the client builds right now; it simply creates the builder and passes it to the client. We need to have the client return it:
public abstract class Framework {
public final void start() {
Config.Builder builder = new Config.Builder();
init(builder);
}
protected abstract void init(Config.Builder builder);
}
public final class MyApp extends Framework {
protected void init(Config.Builder builder) {
return builder.setProperty(...).build();
}
}
The client can hold a reference to the Config, but is unable to mutate it. Although this works, we can see that the client doesn't have any reason to know about the Config. We can remove the client's ability to access the Config by removing their ability to build it, and forcing them to return the builder after they specified the properties (allowing the framework to build it). Simply protect the Builder#build method (as well as Builder's constructor; no reason the client should be able to instantiate it). The final API design looks like:
public final class Config {
private Config(Builder builder) {
}
public static final class Builder {
Builder() { }
Config build() {
return new Config(this);
}
}
}
abstract class Framework {
public final void start() {
Config config = init(new Config.Builder()).build();
//use config
}
protected abstract Config.Builder init(Config.Builder builder);
}
public final class MyApp extends Framework {
protected Config.Builder init(Config.Builder builder) {
return builder.setProperty(...);
}
}
I don't expect this to be used, seeing how a system like this is not needed in scripting. I just thought some programmers may be interested in such development processes.
I also criticise this as being verbose, but it's a step up from "no design". Responsibilities, encapsulation and enforcing a "hard to misuse API".
This also may seem like a complete overkill to those who don't care for strong design, so if you're a "if it works, good enough" kind of developer, this is obviously not a topic for you :P Feel free to leave feedback!