I wrote a Java method
m
as part of a project at work. It was an implementation-detail method, not to be called by the client applications of the project, that needed to be called only in a certain context. I provided a public method p
that set up the context and called m
—but as happens so often, the project had evolved to have a structure more organic than organized, and p
was in a different package from m
, which meant that m
had to be declared public
.My coworker, writing a client application of my project, wanted to invoke the operation that
p
performed, but didn't recall the name of the method he wanted to use. So, using his helpful IDE, he searched. Well, I had given m
and p
similar names—no, I didn't really call them m
and p
, but gave them names describing what they did—and so my coworker's IDE found m
, and he wrote a call to m
in his code. Since he didn't call p
, the context required for m
to work properly was not set up, and m
blew up when he called it, and he wasted some time trying to figure out why before he came to me and I told him he should have called p
instead.In my defense, I had clearly stated in the Javadoc for
m
that it was to be called only from p
, not from the client application. Unfortunately, my coworker searched for just an identifier in his IDE, and the IDE didn't know enough to read the caveats I had put in the Javadoc and wave red flags in front of my coworker when he called the wrong method.(OK, I know what you're thinking. I shouldn't have set up my system so that
m
and p
were in different packages in the first place. This kind of setup—a publicly visible method not meant to be invoked by the public—is one that should have been caught in the design phase, or been solved by something like the export controls in the eternally-not-quite-ready-for-prime-time Java Module System, JSR 277. And in this case, you're probably right, at least in a perfect world. But this problem can also occur where refactoring and modularity don't work, as I show below; so for now, please just accept the problem as legitimate.)Now for the pattern I adopted to solve the problem. I haven't seen any discussion of a general solution to this problem elsewhere, so I'm going to take the initiative and name the pattern: Unconstructable Token.
In Unconstructable Token, a method
m
that is declared public
but should be invoked only from a particular context takes a parameter of a type T
that can be constructed only in that context. m
verifies that the T
is not null
, and gives a sensible error message if it is null
—a runtime enforcement of m
's restricted visibility that is admittedly inferior to a compiletime enforcement, but the best that can be managed if m
must be declared public
. (In practice, this is almost as good as compiletime enforcement, because programmers are unlikely to try to pass null
as a method argument without explicit permission.)A schematic example:
package p1;
import p2.C2.T;
public class C1 {
public void m (C2.T token) {
if (token == null)
throw new IllegalStateException(
"Cannot call C1.m directly; call C2.p");
do something useful
}
}
package p2;
import p1.C1;
public class C2 {
public class T {
T () {
// constructor is *not* public, so can only be
// called from package p2
}
}
// The token doesn't have to be a singleton, but is here:
final T t = new T();
public void p (C1 c1) {
set something up
c1.m(t);
finish up
}
}
The example shows a token with no functionality of its own, but it is often convenient for
C2
to place some of the data or actions needed to coordinate between m
and p
in the token itself.The hoped-for result is that your documentation-ignoring coworkers will quickly realize they can't get a hold of a
T
, and so can't call m
; and therefore they keep searching and stumble on p
, which they should call. If they are so bold as to call m(null)
, they'll get an error message at runtime that points them in the right direction.Of course this pattern is Java-specific. In C++, you wouldn't declare
m public
, but instead declare p
to be a friend
of m
, and you'd be done. But the Java folks don't seem inclined to take the language in the direction of friend
, so that's unlikely ever to be an option for Java.Anyway, are you sold yet, or is Unconstructable Token more antipattern than pattern? I think that depends on context. In the distant, shining world of JSR 277, this system could be a single module that publishes
C2
, but not C1
or T
. m
would still be declared public
, but because it's in a module that doesn't export it, it's not visible to the client application. But in the real world of deadlines and pointy-haired bosses, we can't wait for Java 7 (which will surely include an implementation of the module system—won't it?), and we don't necessarily have the luxury of refactoring the system to put all the relevant mechanism into one package (if that even makes sense), and we probably don't want to get bogged down in something as heavyweight as OSGi (which is an existing module framework specification, but more oriented towards modules that may be added and removed during execution).I may be displaying undue favoritism because I invented Unconstructable Token, but I think it's not so ugly that you need to be ashamed of using it, especially if you document that you're doing so.
Now, as promised, a situation where Unconstructable Token can be indispensable: the case of synthesized code. If you are writing a class loader that modifies or generates code to implement the rules of some framework, you may want the generated code to call methods in the framework (ordinary methods that you write as part of the framework, not generated methods). However, even though the framework methods may perform actions that don't make sense except when called from the generated code, you must make them
public
in Java—otherwise, you won't be able to call them from code loaded by your class loader.If you invoke the generated code in question directly from the framework, you can just pass an Unconstructable Token to the generated code, and the generated code can pass it back when it calls back into the framework.
The solution is more complicated if your framework methods can be called from arbitrary places in the generated code. I haven't dealt with such a situation in practice, but it seems to me you could add some generated code to each class to wind up putting the Unconstructable Token in a
private
variable in each class containing generated code. Since I'm too lazy to figure out whether this really works, or how to do it tidily, I'm going to decree this an Exercise for the Reader. At least, until such time as I need to do it myself.Note that neither refactoring nor modularity gets us anywhere in the case of generated code. Assuming your framework doesn't load itself, the generated code you load with your class loader will necessarily be in a different package from any of the framework packages (because they have different class loaders). And how would you write a module so that it exported a method to the generated code, but not handwritten code in the same package? I'm not familiar enough with the module system proposals to say definitively that it's impossible, but it doesn't sound easy.
No comments:
Post a Comment