Search code examples
javaarchitecturesingletondependenciesdecoupling

Avoiding global state without cluttering constructors


I have a project that is in need of refactoring. It is a Java desktop SWT/JFace application with approximately 40 GUI components with major components controlling the lifecycle of minor components. I want to have good modular design with low coupling, but I can't find a way to avoid global state without having very long constructors.

For example, the current approach to internationalization in the software is something like:

public class Main {
    public static void main(String[] args) {
        MyMessages.loadMessages();
    }
}
public class MyMessages {
    private static ResourceBundle messages;
    public static void loadMessages() {
        messagesBundle = ResourceBundle.getBundle("messages", "en");
    }

    public static void getMessage(String m) {
        return messagesBundle.getString(m);
    }
}

public class SomeComponent extends Component() {
    public void init() {
        String textboxLabel = MyMessages.getMessage("someMessage");
    }
}

Other places in the code use singletons (for data models), which makes it hard to analyse dependencies as the codebase grows.

An alternative approach would be to make MyMessages stateful and pass the instance of it all the way through the hierarchy of GUI components. To me this seems messy, error-prone and not worth the hassle compared to the perceived benefit.

What other ways are there to design this in a way that:

  • Doesn't rely on what are essentially global variables.
  • Makes the dependency relationships explicit.
  • Doesn't clutter the code with lengthy constructors.

Should I consider dependency injection frameworks, or is there another approach that doesn't seem overkill for a small desktop application?


Solution

  • Should I consider dependency injection frameworks, or is there another approach that doesn't seem overkill for a small desktop application?

    For a small desktop app, I would recommend using the dependency injection design pattern, but hold off on using an industrial-strength DI framework.

    In your code, the message helper classes are OK, but your SomeComponent class is definitely not DI friendly:

    public class SomeComponent extends Component() {
        public void init() {
            String textboxLabel = MyMessages.getMessage("someMessage");
        }
    }
    

    SomeComponent is now tied to MyMessages.

    Instead use something like:

    public class SomeComponent extends Component() {
    
        public void setTextBoxLabel(String value) {
            // implementation depends on requirements
        }
    
        public void init() {
            // do something with all the properties that were set
        }
    }
    

    Basically just add setters to your component. This the first part of DI. Make it so all your classes are loosely coupled and each class just trusts that someone is going to take care of setting all it's properties. No need for global state because everything a component needs is going to be injected into it.

    Of course once you do this, your startup code is going to become a nightmare because you have to create all these objects and set all their properties and it's all tied into your application. At which point you start refactoring your startup code and create builders and/or factories which take care of creating the objects for you.

    Let's say one of the components is initialized by a properties file. You create a builder that reads the properties file, instantiates the component, set the components properties and returns the instance. The component knows nothing about properties files. It just has some getters and setters which it knows will be set at startup.

    Later on you decide to use XML as the file format - but for a different app, while still using properties file for the first app. No problem - create a new XML builder that reads the XML file and builds the component - but the actual GUI component remains unchanged because it is completely decoupled from how it gets initialized.

    The key is to review creational design patterns and figure out which patterns (builder, factory etc) help you to reliably create your components while avoiding global state.