Managing Object Lifetimes
There is a myth out there that creating objects is expensive. The result of this is that a lot of applications have objects whose lifetime is too long. Let’s take a web app for example. Most web-apps I have seen have too many long lived objects and not enough request scope objects, which has implication on how data flows through the application. A web app has at least three scopes: application, session and request scope. These scopes are in decreasing lifetime and most of the time they do not overlap (ie the request scoped objects die, before the session objects can die).
Now I believe that each object should have reference to other objects whose lifetime is equal or shorter to the object. In other words, a request can know about session, but it is not a good idea for the session to know about a request. (When I say ‘know’, I mean that it does not have field reference to request, it can have temporary reference through the stack, i.e. method parameter.) The way a long lived object gets a hold of a short lived object is through the stack (passed in through a method parameter).We could generalize this rule to say: Pass in objects of equal and greater lifetime through the constructor and objects of shorter lifetime through the stack.
Now the interesting part is what happens when you start to break this rule. There are two ways to break this rule, lets look at them in turn.
When this rule is reversed (passing short lived objects into a long lived object constructor. i.e. session knows about request) you have a recipe for bugs, since now the garbage-collector will not be able to clean up the objects which are clearly out of scope. There is not much you can do with a request object after the http connection is closed. I see this mistake often, and usually developers solve this through some kind a clean up code. Recently, for example, I came across a game of GO which had a App and the Game class. Now App is lifetime of the application but Game is only for the duration of the game. In the code every time the user wanted to play a new game the App had to go through a cleaning process for a Game object, which was a source of errors. Your first game would behave right but your second game may not. Solution is to simply throw away the Game and instantiate a new one. Now the App is responsible for instantiation of the Game object but the App does not keep a reference to the Game object (beyond the local reference on the stack.)
More common violation is to not have an appropriate life time object in the first place. For example let’s say that you have a web app and you don’t have any classes which are meant to be request scope, such as a Servlet. A servlet is application scoped, as a result, you cannot inject the request/response into the constructor, instead you have to pass them through a stack, which is exactly what happens. Now not all code is in your servlet, therefore your servlet needs to collaborate with other objects, but most of them are application scoped as well. Now let’s say that you have three application scoped objects A, B and C, such that A calls B, B calls C and A does not know about C directly (i.e. A->B->C). Let’s say that C needs the http cookie. The only way for me to get the cookie into C from my servlet is to pass it in as an argument to A, which than passes it to B, which than passes it to C. Sooner or later you will realize that this is a real pain, since every time C needs a new object reference you have to modify all of these unrelated objects. Therefore you will create a context object which knows about cookie and all of the other objects and you will be proud of yourself how clever you are. But a context is solving the wrong problem. The real problem is that C’s lifetime is wrong. If C’s lifetime would be that of cookie (request scope) than the Servlet could just pass it in directly like this:
HttpRequest request = ...;
C c = new C(request.getCookie());
B b = new B(c);
A a = new A(b);
Now this is great since A and B are no longer involved in the process of passing along the cookie. After all neither A nor B cares about the cookie. It also means that if at some later point in time C needs additional object such as request-path C can just ask for it in the constructor without having to change anything in A nor B.
Servlet becomes a place where the long lived object (servlet) meets short lived object (our ABC object graph). This means that whenever you have to cross the object boundary in reverse you need a factory, which is what our servlet does.
Therefore, whenever you get into situation when you have to pass objects through many layers, or that you create a context and than pass the context through the layers your layers are probably long lived and need to have their lifetime adjusted. You can generalize this and say that: Whenever a class has a reference to another object but does not directly dispatch methods on that object, than the collaborator which needs the object is of wrong lifetime.