Alternatives to global variables and passing the same value over a long chain of calls
The old, good global variables are the wrong solution to a real problem: you want some information to be available in a large context. In Object-Oriented languages like Java we often do not have something we call a global variable. We instead lie to ourself using public static variables or singletons and denying they are exactly the same thing. No, we are too much object-oriented and too good developers to be using global variables.
Passing a value to all single method call: much better?
Ok, so you repent and decide to give up global variables. What do you do? You pass a value explicitely. Cool. And then you pass it to another method and to another and the same value is propogate over and over until it is available in many places of your application. Much better, eh? You have polluted the signatures of far too many methods and should you ever decide to change the type of the value passed, that would require a lot of work.
You have a bunch of methods just passing around the information. Many of them do absolutely nothing with that value, but still need to receive it to be able to pass it along the chain.
Consider a concrete example: I was working on tool for analyzing Java code and some objects required an instance of a TypeResolver: an object that given a name (such as java.lang.String or my.shiny.Class) would provide for them representations of types. Good.
Let’s call the classes using directly a TypeResolver typeResolverUsers. Now, whoever was using the typeResolverUsers (let’s call them the usersOfTypeResolverUsers) needed a TypeResolver to pass it to the typeResolverUsers it was invoking. So each usersOfTypeResolverUsers asked for a TypeResolver at construction time and store it, to later pass it to classes which actually needed it. So now whoever was instantiating one of the usersOfTypeResolverUsers needed to have a TypeResolver reference. And so on, until far too many classes were aware of the existence of the TypeResolver class.
That is not nice to refactor and it pollutes your methods. I solved this particular case using a Registry. It was not flawless and not so straightforward so I was wondering: is there a better solution?
Another example: Position and WorldSize
Let’s think about another example: we want to represent a Position on a world map (think about my beloved world generator: WorldEngine). Now the Position is of course defined by two values: x and y. However given it is a world map we want to be able to wrap left-right. So to perform certain operations we need to access the world size: if we know that the world is 500 cells wide, when we move from a Position with x=499 to the right we end up having x=0.
In this scenario we could:
- use a global variable: it means that in our program there is one possible world size. It is stored in some place accessible from the whole application. That works if this assumption holds. If we instead are working on several worlds at the same time we need an alternative.
- we could store the world size in the Position instance. That means that in every occasion in which we want to create a Position we need a reference to a world size. Given the application deals all the time with Position instances that means that many methods (perhaps most methods) will get a WorldSize instance and pass it down the chain.
I am not happy with neither of these alternatives. I would like to have something that is almost like a global variables but somehow having a limited and controlled scope. Or if you want, I would like to implicitly pass some contextual information without polluting the signature of all methods.
I think that a Position is really defined only by x and y. It needs to know the WorldSize to perform certain tasks, but this is a contextual information, not something that is controlled or owned by Position.
Contextual information: what the heck is that?
For this reason I am working on the concept of contextual information. For now I have implemented it like this:
- we can declare that certain values could be context dependant: we would specify a name and a type for each of them
- we could assign values for a certain context-dependant variable according to a dynamic scope: it means that we would make the value available not only in a certain block of statements but also to all the functions and methods invoked in that block, recursively
- the value would be accessible only in a given thread (yes, ThreadLocal to the rescue)
Examples
This is the current syntax as implemented in my programming language, Turin:
context WorldSize worldSize void myComplexOperation() { ... // here worldSize is empty ... context (worldSize = WorldSize(600, 400) { // here worldSize has a value mySubOperation() } ... // here worldSize is empty ... } void mySubOperation() { // here I can access worldSize from the context // because I was called in a context which had a value // for worldSize if (x > context.worldSize.get().width) { ... } }
Alternative solutions
There are many ways to make values available to your application without using explicitly global variables. One way it to use a Singleton, another is to declare static variables.
Another solution is the RegistryPattern:
A registry is a global association from keys to objects, allowing the objects to be reached from anywhere. It involves two methods: one that takes a key and an object and add objects to the registry and one that takes a key and returns the object for the key
Pros & Cons
With respect to a global variable we are able to limit the scope of a value, to a context which is not static but dynamic, anyway totally unrelated pieces of code could not mess up with the same global variables we are using.
The main issue with that solution is that we cannot statically determine if a value is available or not in a given context. However we return an Optional leaving the responsibility to the user to verify if the value is present or not.
With respect to the ContextObject pattern we offer type checking: we declared the type of a context value so we can verify that only compatible values are ever assigned to it.
Reference: | Alternatives to global variables and passing the same value over a long chain of calls from our JCG partner Federico Tomassetti at the Federico Tomassetti blog. |