萍聚社区-德国热线-德国实用信息网

 找回密码
 注册

微信登录

微信扫一扫,快速登录

萍聚头条

查看: 886|回复: 1

[Link]When Static Methods and Code Collide

[复制链接]
发表于 2006-9-7 10:04 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册 微信登录

x
When Static Methods and Code Collide

Resist the urge to overuse static methods. Promote greater application flexibility through libraries and interfaces

by Daniel F. Savarese

http://www.ftponline.com/javapro ... ne/columns/proshop/
Die von den Nutzern eingestellten Information und Meinungen sind nicht eigene Informationen und Meinungen der DOLC GmbH.
 楼主| 发表于 2006-9-7 10:11 | 显示全部楼层
The release of a final version of J2SE 1.5 is just around the corner, and Sun is up to its old tricks again. One day I visited java.sun.com only to find J2SE 1.5 was no more. It is now J2SE 5. Furthermore, the J2SE 1.5 SDK is now the J2SE 5 JDK with internal version number 1.5.0. Just when we were getting used to accepting that Solaris 2.7 is Solaris 7, SunOS 5.8 is Solaris 8, and JDK 1.2 is J2SE SDK 1.2, now we've got J2SE 5 and are back to a JDK instead of an SDK, but with some internal version numbering business tacked on. These numbering games aren't worthy of note other than to put you on alert that documentation will surely be inconsistent and cause some short-term confusion.

A J2SE news item meriting attention is that Sun has started making available snapshot releases of JDK 1.5 (or is it 5?). This move is extremely important because it gives developers access to new features and bug fixes that are in the development tree but have yet to make it into a formal release. Also, it gives Sun the benefit of more user testing, with the anticipated result that more bugs will be detected sooner. A note on either java.sun.com or www.java.net from one of the J2SE team members called the new practice an experiment, but my guess is they will not discontinue the practice after they reap its benefits. The release of J2SE 5 may be the most important event for Java developers since the transition from JDK 1.1 to J2SE 1.2. Therefore, Sun's effort to involve developers more in the release cycle is laudable.

Having dispensed with the commentary, it's time to talk about code. What I perceive of as an overuse of static methods in Java programs has long concerned me. Time after time I find my hands tied by static methods that cannot be overridden or adapted in an object-oriented vein. I don't mean to say that static methods should be avoided altogether. It just seems to me that they are often used when inappropriate. I'll concede that it's hard to lay down clear guidelines about when they are appropriate and when they are not; so keep in mind I am expressing my personal opinion based on my experience, which may not agree with yours.

Go to the Library
Despite the variety of programming work I've done over the years, I have a bent toward library and API design. Reusability, flexibility, genericity, maintainability, portability, usability, and testability are some of the factors I emphasize in design. Also, my experience as an application programmer influences how I approach the design of class libraries. I don't believe class libraries should force constraints upon an application that should best be left in the hands of the application.

For example, I don't believe class libraries should have dependencies on environment variables or system properties unless absolutely necessary. These dependencies are instances of global behavior that influence how a library behaves to all aspects of an application. If part of an application requires a library to behave in one manner at the same time another part of the application requires it to behave in another manner, global configuration throws a wrench into the works. It is less restrictive to allow the application to instantiate library artifacts and configure them through an API, thereby allowing the application to implement its own environment variable, system property, or other configuration system.

Similarly, static methods preclude the ability for a program to customize behavior or implement generic algorithms using polymorphism. It can be tempting to lock up stateless algorithms into static methods instead of creating interfaces and class implementations. If you resist the temptation, you can make a class library more flexible and make the job of an application developer easier.

To illustrate the point, let's look at sorting. If you implement sorting in a static method, you cannot interchange sorting algorithms without changing the method call in application code. Instead of using a static method you can define an interface such as:


  1. public interface Sort {
  2.   public void sort(
  3.     Object[] array, Comparator
  4.     comparator);
  5.   public void sort(
  6.     Object[] array, Comparator
  7.     comparator, int start,
  8.     int end);
  9. }
复制代码


By using this technique in a library, you allow the application developer to write classes and methods in terms of the interface instead of with direct calls to a static method. Listing 1 shows an implementation of the quick sort algorithm using this approach. Notice that the object does not store any state. Java does not support references to methods the way C supports function pointers. Therefore, if you want to pass around a stateless method as a parameter, you have to associate it with a class instance.

Something to keep in mind about static methods is that you can achieve the same effect at the application code level using a static final member. For example, instead of throwing this code into a library:


  1. public final class Util {
  2.   public static final void
  3.     sort(...) {
  4.     ...
  5.   }
  6. }
复制代码


you can allow the application developer the freedom to write:


  1. public final class Globals {
  2.   public static final Sort SORT =
  3.     new QuickSort();
  4. }
复制代码


Instead of calling Util.sort, the application would use Globals.SORT.sort. To change the default sorting algorithm in a future release, only the initialization of Globals.SORT would have to be changed. Alternatively, the application developer may prefer to write:


  1. public final class Util {
  2.   private static final Sort SORT =
  3. new QuickSort();
  4.   public static final void
  5.     sort(...) {
  6.     SORT.sort(...)
  7.   }
  8. }
复制代码


Just Enough Choices
Even though a static method is used, it is an application-level method and not a library mandated one. There are plenty of other choices depending on how the application uses sorting. One of the more flexible methods would be to implement a factory interface for generating sorting algorithm instances. That way you can instantiate the algorithm best suited to the data you are sorting instead of using hard-coded references to global variables or static methods. If a library implements sorting in a static method, it locks the developer into one way of doing things, or at the very least creates more work. As a workaround, clever programmers can write their own interfaces and wrap static method calls inside of implementations. For example, you can wrap java.util.Arrays.sort with:

  1. public class DefaultSort
  2.   implements Sort {
  3.   ublic void sort(Object[] array,
  4.     Comparator comparator, int
  5.     start, int end) {
  6.     Arrays.sort(array, start, end,
  7.       comparator);
  8.   }
  9. ...
  10. }
复制代码


There's nothing wrong with a library presenting static methods as a convenience, but too often it is done without also providing the more flexible mechanism alongside it. It's better to give library users more choices rather than fewer. Otherwise, you are less likely to be able to meet unanticipated needs. Still, it's tricky to keep more choices from becoming too many choices.

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 How to Build a Beowulf (MIT Press,1999). Contact Daniel at java-pro@fawcette.com.
Die von den Nutzern eingestellten Information und Meinungen sind nicht eigene Informationen und Meinungen der DOLC GmbH.
您需要登录后才可以回帖 登录 | 注册 微信登录

本版积分规则

手机版|Archiver|AGB|Impressum|Datenschutzerklärung|萍聚社区-德国热线-德国实用信息网 |网站地图

GMT+2, 2024-5-10 16:06 , Processed in 0.053631 second(s), 19 queries , MemCached On.

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表