4

According to Why is Global State so Evil?, as I understand, global state is bad and I should use dependency injection instead. That means, for example, a mobile app that use bundles of app level data as follow:

public class UserData{
    private static UserData userData=new UserData();
    public UserData getInstance(){
        return userData;
    }

    private String sessionId;
    private String surname;
    ... (other fields with setter and getter)
}

public class SomePage{
    private UserData userData;
    public MainPage(){
        this.userData=UserData.getInstance();
    }
    public void onLogoutButtonPressed(){
      Http.post(Constant.LOGOUT_URL,"?sessionId="+userData.sessionId);
    }
}

is an bad example and need to be fixed to use dependency injection.

However, as far as I know and according to https://softwareengineering.stackexchange.com/a/319609, the "spirit" of dependency injection is , an object receives target objects externally instead of that object creates target objects by itself, eg:

non dependency injection version:

public class SomePage{
    public UserData userData;
    public SomePage(){
        this.userData=new UserData();
    }
}

dependency injection version:

public class SomePage{
    public UserData userData;
    public SomePage(UserData userData){
        this.userData=userData;
    }
}

Then, I think my "global state" version:

public class SomePage{
    private UserData userData;
    public MainPage(){
        this.userData=UserData.getInstance();
    }
}

also doesn't create UserData at all. And I think the "spirt" of using global state and injecting object from constructor is quite similar : getting required objects externally, just their ways to get that external objects are different. So my question is, why is using global state doesn't consider as dependency injection even if it gets object externally?

1
  • Is it possible that they're both right? That state should be gotten externally, but also, not from global variables?
    – user253751
    Jun 30, 2020 at 18:05

3 Answers 3

4

What you're describing is a Service Locator, not Global State

The pattern you're describing in your code is often called the "Service Locator" pattern, and is a subset of dependency injection, one that is often maligned for various reasons. You're accessing it via a global, static variable (which is a problem in itself, if you want to switch containers/locators) but it's still doing DI.

When you call UseData.getInstance, you're essentially accessing a single, global, shared dependency container and asking it for the implementation of UserData. It's not far different than putting it in your method or constructor signatures and asking the DI framework to pass it when needed. It's flexible and allows you to request an external dependency on demand, and is supported by many DI frameworks in many languages. Essentially, it's still dependency injection, just a different usage pattern.

ServiceLocator is considered a bad way to do DI

The reasons it's considered an anti-pattern by many is that it makes it hard to tell what a class's dependencies are. If you use constructor injection, it's easy to see at a glance what a class's dependencies are from its ctor signature, and you can tell if you're going to have problems with mutual dependencies. If you use property/field injection, you likewise have the dependencies listed in one place. Method injection is the same, but for method scope, which is also fine.

But with a service locator, you can have a code flow somewhere deep in your logic that suddenly "remembers" it needs UserData and requests it from the ServiceLocator, and you might miss it when evaluating your architecture and its dependency graph. It leads to more runtime errors because you can't have a fully instantiated object graph early in your runtime, and harder to evaluate it statically.

3

One more practical reason in addition to the answers already present.

OK, let's look at the following variants you posted:

// 1
public class SomePage{
    public UserData userData;
    public SomePage(UserData userData){
        this.userData=userData;
    }
}

// 2
public class SomePage{
    private UserData userData;
    public MainPage(){
        this.userData=UserData.getInstance();
    }
}

Now, lets do some unit tests (you do use unit tests, right?):

// 1
UserData userDataMock = Mockito.mock(UserData.class);
... set up the mock, so that it returns "Joe" for getUser() ...
SomePage testObject = new SomePage(userDataMock);
String output = testObject.render();
Assert that output contains "Joe"

// 2:
...hmm, having a bit of a problem here...

Use Powermock to override the result of a static call like UserData.getInstance() with a mock?
What if the page also calls AddressData.getInstance()?

Have a settable global instance in UserData?
What about other tests? Don't forget to reset the state after the test...

DI does not have these kind of problems. Global state has. Simple as that.

0

Dependency injection is about letting client code decide which dependent modules should be used in a module. You can practice DI with global state, should you wish to do so, by injecting an object that encapsulates access to global state:

SomePage somePage = new SomePage(globalUserData);

If I were to do that, I would probably not choose singleton pattern to begin with. Instead you could have

public interface UserData { ... }

with a package private implementation

class GlobalUserData implements UserData { ... }

Your top level module would then create the object and pass it around as a dependency of type UserData. Low level modules would not be served any handle to get or create an instance of UserData themselves. If low level modules don't need write access, then provide nothing but getters.

At some point you might like to replace your GlobalUserData with a fancier and more flexible solution. It would be easier now that your low level modules don't have any dependency to it. They just consume UserData.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.