|
When Static Methods and Code Collide (Continued)
Static factory methods are a major culprit for shackling application developer options. There's a pearl of conventional programming wisdom I disagree with that states static factory methods serve a different purpose than factory classes. The justification is that static factory methods are intended to be used in lieu of constructors. I contend that to get the same result as using a static factory method, an application need only declare a static final instance of a factory class. If desired, an application developer can always hide the instance by making it private and wrapping it with an application-level static factory method as in one of the previous examples. There's little need for a class library to force static factory methods upon the application developer unless it needs them to provide some kind of default bootstrapping that cannot be implemented in any other way or it is providing them as a convenience alongside factory interfaces. However, it can be very appropriate for application-level code to provide static factory methods.
There are too many libraries out there that make it very difficult—or impossible—for programmers to use multiple implementations of a piece of functionality in different parts of the same program. I don't mean to single out the Java core APIs, but an example of this problem is the way the socket API was originally designed in JDK 1.0.2. Socket.setSocketImplFactory forces a single socket-creation factory upon the entire application. Worse yet, recalling the earlier example of global environment variables and system properties, the java.net package relies on a host of system properties. For example, if you want to use a SOCKS proxy you can set the socksProxyHost and socksProxyPort properties. What happens if you want some socket connections to use the proxy and others not to? You have to avoid the java.net configuration properties and implement your own SOCKS support.
Going Global
In general, implementing global behavior in a class library creates more work for the application developer. I believe there are fundamental differences between how you write class libraries and application code to facilitate reusability and maintainability. An application may itself be divided up into many subcomponents that are themselves libraries. The code that stitches all of the pieces together is what I consider the application.
Global behavior belongs in the application code where it does not need to be reused. A nice benefit of test-driven development, whether it's of the "write the test first" versus "write the test after" style, is that it discourages the implementation of global behavior in class libraries. It's more maintainable and reliable to implement a generic test that can test any number of different sorting interface implementations than it is to write multiple tests that test the same number of different static methods.
In the end, a particular technique is only good or bad if you find it to be a help or a hindrance in a particular situation. I recently found myself bemoaning Java's lack of support for multiple inheritance of implementation. Never mind that I rarely use that form of multiple inheritance in C++. I found myself in a situation where the most modular and maintainable code required the ability to use multiple inheritance of implementation. I ended up having to duplicate method declarations in a bunch of classes implementing the same interfaces and delegate method calls to objects that belonged to classes that would have served as one of the inherited superclasses. The result was that every time a new method was added to one of the delegate classes, the method had to be added to all of the delegating classes.
Aspect-oriented programming provides a way to modularize this concern in a single aspect, which may in fact be a better approach than multiple inheritance. The adage that multiple inheritance is useless sounds good only until not having it makes your life more difficult than you want it to be. (Disclaimer: I'm not suggesting that Java should have multiple inheritance of implementation. There are very good language implementation reasons for having excluded the feature, especially considering how rarely it's needed.) Best practices for programming are best practices only most of the time. There are always situations where a recommended practice blows up in your face and is entirely inappropriate. The challenge of programming is just as much knowing when to use a particular technique, as it is knowing when not to use it. The techniques best applied to building applications may not be the same as those best suited to building libraries.
About the Author
Daniel F. Savarese is an independent software developer and technology advisor. He has been the founder of ORO Inc., a senior scientist at Caltech's Center for Advanced Computing Research, and vice president of software development at WebOS. Daniel is the original author of the Jakarta ORO text processing packages and the Jakarta Commons Net network protocol library. He is also a coauthor of (MIT Press,1999). Contact Daniel at .
Back to top
|