1

According to Why is Global State so Evil?, I believe we should avoid global state, so suppose I have an App that count user clicks in all pages like it:

public class GlobalState{
    public int clickCount=0;
}
public class Page1{
    public void onButtonPressed(){
        GlobalState.clickCount++;
    }
}
public class App{
    public static void main(String[] args){
        Page1 pageOne=new PageOne();
        pageOne.show();
    }
} 

I know it is bad, I should change the GlobalState as parameter:

public class Page1{
    public IGlobalData globalData;
    public Page1(IGlobalData globalData){
        this.globalData=globalData;
    }
    public void onButtonPressed(){
        globalData.setClickCount(globalData.getClickCount()+1);
    }
}
public class App{
    public static void main(String[] args){
        IGlobalData globalData=new GlobalData();
        Page1 pageOne=new PageOne(globalData);
        pageOne.show();
    }
} 

Which requires us to add additional code (eg:parameters) to access the click count, reduce the chance of unrelated pages change click count accidently.

However, what I don't know is, why is it called "alternative of global state"? Because I think "clickCount" state still exists in the memory of computer at the life time of the app running, which global states still exists. And I think the global state is essential for a user requirement and cannot be removed, otherwise how can I record the user click during the app using? I think the consequence of avoid global state in this case is (say to the users): "Don't create an app that counts user clicks of all pages" !

So my question is, why is Dependency Injection called "alternative of global state"?I think Dependency Injection is just helping us to access global states in better way, but not "avoid global state" actually because the global state still exists. Access global states in better way doesn't mean no global states. Or am I missed something about Dependency Injection?

13
  • 6
    "Global" means "exists in the program's global scope" -- the object from the second example is not in the program's global scope, it is created within the scope of the main method and then passed down into the scope of a Page1 instance; that means its scope is fully under your control. (I.e. you must explicitly pass the object into any function or class which needs to access it, which keeps it constrained and keeps the program's dependency graph under explicit control). On the other hand, there is no such control for methods or classes which depend upon global scoped objects. Jul 7, 2023 at 10:09
  • 2
    In short, it has nothing to do with whether the dependency is stateful, it's about maintaining control over that dependency and its accessibility. (Dependency injection is "inversion-of-control" so the dependency direction is controlled from the outside) Jul 7, 2023 at 10:15
  • @BenCottrell, I think a crucial subtlety is that DI only enables some additional control, it doesn't actually enforce additional control. You don't need to pass the shared object itself into the contexts which use it - you only need to inject something capable of getting a reference to the shared object.
    – Steve
    Jul 7, 2023 at 11:47
  • 1
    "Because I think "clickCount" state still exists in the memory of computer at the life time of the app running" That's not a useful definition of global state. At the extreme, in a language that lets you use pointer arithmetic to access values at any arbitrary address, then literally all values end up as this definition of "global state" (even local variables and function parameters, which are accessible from the stack!)
    – Alexander
    Jul 7, 2023 at 14:15
  • 1
    The discipline of how DI is used carries all the weight - which was exactly the problem with global variables, that too many lacked enough discipline! So my point, to be clear, is that you're overselling the guarantees of DI and fabricating false contrasts. Both allow external control of the dependency. Neither ensures anything by default. Both demand a large dose of discipline to use correctly, and inflict the same risks and errors when that discipline is lacking (2/2)
    – Steve
    Jul 9, 2023 at 16:04

3 Answers 3

4

It's crucial to remember that the diktat against using the global scope is primarily a diktat against unstructured/disorganised access to shared variables.

The primary way in which programmers in the past accessed shared variables in an unstructured/disorganised way, was by declaring globals and reading and writing them freely from all over.

But the same result can also be achieved without the global scope, by simply making an object accessible from anywhere (or at least, from an unreasonably wide area of the program) by passing around direct or indirect references.

There's also a secondary issue in OO languages, which is that the global scope is avoided because it (potentially) prevents the objects inside a single shared library being used more than once. It's relatively common that once inexperienced developers start using global variables, they do so in a way that ties the whole codebase to being used by a single application/task at a time.

This has potentially become less relevant in recent years as libraries are often not shared between applications nowadays (due to so-called "DLL hell"), but in the past when memory was more scarce, there was greater importance in loading libraries into memory only once and ensuring that the same code could service callers from multiple applications.

3
  • 1
    On the other hand, it has become more relevant in recent years with the widespread deployment of multicore processors and deep IO queues, leading to much more significant use of parallel and concurrent threads/tasks within a single application. Jul 7, 2023 at 13:09
  • Objects not being usable from multiple threads because they share state that is either not safe or not efficient to access in a parallel manner is a major issue. Jul 7, 2023 at 13:12
  • @user1937198, certainly, although to be clear, my point under the second heading was not that globals in shared libraries are thread-unsafe, it's that they would share application-specific data between completely unconnected applications where sharing of variables was never intended to occur - this is because globals are allocated per-executable. Thread-unsafe code is usually a very advanced form of unstructured/disorganised access under the first heading, rather than a case of inappropriate sharing under the second heading (although it can happen that threads wrongly share variables).
    – Steve
    Jul 7, 2023 at 15:09
0

If we consider a standard web app as an example, it is likely that it will have some global state for things like:

  • Initialization parameters/config.
  • DB connections / connection pools.
  • Logging.
  • Statistics/Performance metrics.

If you are using a framework it will often abstract most of the details of the shared/global state away - for example injecting configuration parameters/objects into the classes that need them.

Taking your example (click count) this could be re-located into a number of places depending upon your use cases, it could:

  • become a record in a database.
  • be calculated in a data warehouse that ingests the logs.
  • be handed as part of a statistics/metrics package.

The primary advantage of re-locating this "global state" into a system that is designed to handle it, is that it provides a mechanism for you to scale your app servers horizontally (add additional app servers). Global state is only global to (typically one process and) rarely more than one machine.

As others have stated, having a big blob of code that writes to database/store from random locations will make it difficult to trace access patterns, it's still worth creating useful classes/abstractions to group database operations logically.

0

Global state and static memory are not the same thing. Once state becomes global you can access it from anywhere. No UML diagram or method signature is going to help you track down where it's getting used. You have to dig through the code.

Static memory, however, is not something you get to touch from anywhere just because it's static. Static is just about it's allocation and lifetime. But if no one gave you access to it then it doesn't matter if it still exists. If you can't access it without something handing it to you then it's not global.

public class App{
    public static void main(String[] args){
        NotGlobalState notGlobalState1=new NotGlobalState();
        Page page1=new Page(notGlobalState1);
        page1.show();
        page1.onButtonPressed();
        page1.show();
        NotGlobalState notGlobalState2=new NotGlobalState();
        Page page2=new Page(notGlobalState2);
        page2.show();
        page2.onButtonPressed();
        page2.show();
    }
} 

Which either shows:

0
1
1
2

or

0
1
0
1

Depending on if clickCount is static.

Here we see two pages accessing state that will live as long as main. Yet they aren't sharing the same variable unless you declared clickCount static. But either way, with encapsulation there is no way to access clickCount globally. It's protected from being accessed from places it was never sent. Static or not.

public class Page{
    public NotGlobalState notGlobalState;
    public Page(NotGlobalState notGlobalState){
        this.notGlobalState=notGlobalState;
    }
    public void onButtonPressed(){
        globalState.setClickCount(notGlobalState.getClickCount()+1);
    }
    public void show() {
        System.out.println(notGlobalState.getClickCount());
    }
}

And if you have to send it to access it then it's not global. Even if it is static:

public class NotGlobalState{
    private static int clickCount=0;
    //private int clickCount=0;
    public int getClickCount() { 
        return clickCount; 
    }
    public void setClickCount(int clickCount) { 
        GlobalState.clickCount = clickCount; 
        //this.clickCount = clickCount;
    }
    public void click() {
        GlobalState.clickCount++;
        //this.clickCount++;
    }
}

Static or not, it's encapsulated. So you have to be handed a reference to this to mess with it. Thus, not global. Even if it lives as long as main does.

Avoiding globals is all about formalizing access. You can make it live as long as the app without giving it to everything.

Your Answer

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

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