-
Notifications
You must be signed in to change notification settings - Fork 36
BRJS Initialization Problem
Within BladeRunnerJS we are adhering to a couple of design maxims that by themselves are good, but which have an unfortunate side effect. These are:
- Classes should fully initialize themselves within their constructor, since state within classes should be avoided wherever it can be.
- No static state should be be allowed, since it makes testing and re-factoring more difficult.
The unfortunate consequence of these decisions is that it increases the need to provide partially initialized object instances to other objects within their constructor. For example, in the following class:
public class MyClass {
private Foo foo;
private Bar bar;
public MyClass() {
foo = new Foo(this);
bar = new Bar(this);
}
public Foo getFoo() {
return foo;
}
public Bar getBar() {
return bar;
}
}
Foo
might need to invoke getBar()
on MyClass
within it's constructor, even though it's not yet initialized. Although in this simple example we can just re-order stuff, in a large code base, and particular one with plug-ins written by third parties, this ceases to become an option.
For core code, we can deal with the problem of passing partially initialized objects by a combination of re-ordering and/or lazy initialization. For plug-ins however, this is clearly not scalable. Here's a UML of all the plug-in interfaces to jog your memory:
Instead, since plugins are not passed a reference to brjs
within their constructor, they can all be safely constructed in isolation. The order in which the setBRJS()
methods should subsequently be invoked on the plug-ins is then based on the dependencies they have on the other plug-ins within the system.
We might be tempted to add a method like this to the Plugin
interface:
List<Class> getDependentPluginClasses()
But this only allows coarsely grained dependencies to be represented (from one class of plugins to another), and so we are likely to frequently encounter unresolvable circular dependencies within our dependency graphs. Additionally, a system that requires developers to voluntarily describe dependencies, and which doesn't fail-fast when these dependencies are incorrectly described, will tend to be full of inaccuracies that further sour the process.
Even if there was a mechanism that allowed developers to describe finely grained dependencies, and it was always used correctly, this may still lead to dead-locks that could otherwise be avoided by taking advantage of the fact that some plug-ins may not actually be needed from the very start anyway.
What we really need is a lazy initialization mechanism that does the right thing without developers having to be aware of it. As it happens, this can be done by using Virtual Proxies, which would cause the setBRJS()
method to be invoked on the underlying object as soon as the object is put to use. This will lead to a cascade of setBRJS()
method invocations as plug-ins naturally depend on each other. In such a system, we will have an unavoidable circular dependency only when an object that is still mid-initialization has one of it's methods invoked again.
A down-side to this approach is that it will cause the instanceof
operator to fail, and so a replacement instanceOf()
method could be added to the Plugin
interface in its stead:
boolean <P extends Plugin> instanceOf(Class<P> classRef)
Another potential down-side to this approach would be if it caused a flurry of plugin initialization as plug-ins sought to find other plug-ins they were interested in, regardless of the fact most of these will not have been put to use. Again, dead-lock would quickly ensue. To overcome this, we can define a set of identifier methods that don't cause object initialization, and which can be safely invoked before setBRJS()
has been invoked. These probably include the following methods:
Plugin.instanceOf(Class<P> classRef)
CommandPlugin.getCommandName()
FileTransformPlugin.getMimeType()
FileTransformPlugin.getFileExtensions()
TagHandlerPlugin.getTagName()
ServletPlugin.getMimeType()