fixthissite Posted May 10, 2015 Share Posted May 10, 2015 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! 2 Quote Link to comment Share on other sites More sharing options...
Flamezzz Posted May 10, 2015 Share Posted May 10, 2015 My god so much effort I'm personally more a fan of: don't touch the variables/methods starting with an underscore, if you do anyway it's your fault. Config? just pass a dict/map.But it's nice to see a good Builder example, been a while since I looked into design patterns Quote Link to comment Share on other sites More sharing options...
fixthissite Posted May 10, 2015 Author Share Posted May 10, 2015 (edited) My god so much effort I'm personally more a fan of: don't touch the variables/methods starting with an underscore, if you do anyway it's your fault. That doesn't enforce any compile-time safety, which is a pretty big factor in API design. Imagine if the entire JDK was built without compile-time saftey measures; the cognition required to write a program would be rediculous. It's best to inform the developer of a problem as soon as it happens (compile-time). If not possible, inform them at runtime (excpetions). Logical errors should be prevented at all costs, reducing the amount of "wtf moments" (not being able to easily explain the result of the code) and undocumented rules Edited May 10, 2015 by fixthissite Quote Link to comment Share on other sites More sharing options...
LeBron Posted May 10, 2015 Share Posted May 10, 2015 Somebody give this man Developer rank already, I have a feeling he'll make this client better than it has ever been before! 1 Quote Link to comment Share on other sites More sharing options...
lare96 Posted May 14, 2015 Share Posted May 14, 2015 (edited) I just used a map with a forced key type of Enum<E> for O(1) get, put, and contains but this is really nice. Edited May 14, 2015 by lare96 Quote Link to comment Share on other sites More sharing options...
fixthissite Posted May 20, 2015 Author Share Posted May 20, 2015 I just used a map with a forced key type of Enum<E> for O(1) get, put, and contains but this is really nice. That used to be the prefered way of avoiding tons of parameters (pass a map) until the builder pattern came around. A map doesn't enforce concrete configuration. When the client is setting the config using a map, they are unaware of what configuration options are available; it requires you to document what SHOULD go in the map. I actually came across a question on StackOverflow about this. The answers all suggest using a more type-safe solution. Maps aren't bad, and they are definitely less verbose than the builder pattern. But compile-time safety is a HUGE aspect. It's the reason why Oracle is choosing Jigsaw over OSGi for modularizing the JDK; OSGi enforces runtime modularization while Jigsaw enforces compile-time modularization Quote Link to comment Share on other sites More sharing options...