CHAPTER 5

image

Do Not Expose More Than You Want

There are many attributes of good API design, and this book will discuss many of them. But let’s start with the most trivial advice. This advice is widely known, explained in every good design book, yet so important that it deserves to be repeated all the time: the less you expose, the better.

Some API designers just love helping others and believe they’re doing so by putting a lot of helper methods and utility classes in their API. They make every class public. A lot of their classes have members that are either public or protected. These designers believe that one day someone might find their exposed functionality useful. Though there’s nothing to be said against altruism, these designers often cause more problems than they solve. Always remember that it’s much easier to add a new class or method to an API than to remove it. Furthermore, the more you put into an API, the more you have to maintain in a compatible way. The more that’s visible, the less flexibility you have in changing the internals of the implementation. Exposing more than is needed can, in fact, prevent future improvements in the API.

Fundamental dilemmas involve deciding whether something should or shouldn’t be part of an API. The answer to that, at least according to the NetBeans project “methodology,” is to construct use cases. A valid use case is the justification for a class or a method in an API. If there’s no use case for a given method or class, or if the use case is slightly suspicious, then it’s better to leave that element out of the API. One way of doing so is to make it package private. When a user of the API complains and requests the element to be visible, you can still make it visible. However, don’t do so sooner than requested and not without a convincing use case!

THE LESS SKILL, THE MORE VERBOSE THE API

My experience shows that the less skilled an API designer is, the more elements the API exposes. In many cases, this can be attributed to a lack of understanding of use cases. Lack of understanding results in making an existing method or class public whenever some functionality from a library is needed. This style of evolution, where there is no strategy at all, will almost certainly lead to a set of isolated API elements that are randomly glued together. An absence of use cases indicates a lack of strategy and leads to a cluttered result.

Less experienced designers, lacking the wisdom that comes with maintaining an API over several years, often don’t realize that everything they leave in an API can get misused. Every method and class exposed by an API will be misused—in other words, used in a different way than intended by its author. Almost every API designer I know has observed this to be true. And almost every API designer has come to the same conclusion: the longer you spend designing an API, the less you are willing to expose.

So, the first piece of advice is to remove everything that can be removed prior to the first release of an API. Realistically, not everything will be removed as a result of this advice. Behind every altruistic will to share there has to be some hidden need—something that drives and motivates the API’s design. The API designers feel the need for their work and they are eager to share it. I know that feeling; I’ve felt it many times. It feels good; however, you need to keep in mind that publishing an API is just first the step. By publishing it, you promise to do much more in the future. You promise to develop the API compatibly, to maintain it properly, and much, much more. In fact, by taking the first step you set a direction and promise that every other step will follow that direction. There’s nothing wrong about taking such a step if the API designers feel there’s a need to create the API. On the other hand, we need to learn how to prevent promising “directions” when we’re just “walking.” The simplest and most effective solution is to hide the fact that we’re taking steps from our end users.

A Method Is Better Than a Field

The first technique we’ll look at for hiding the API is the practice of hiding fields. It’s better to use methods—typically getters and setters—to access fields than to expose them directly. This is true because, first, a call to a method can do a lot of additional things, while accessing a field can only read or write the value. When you use getters, you can do lazy initialization, synchronize the access, or compose the value using a computational algorithm. Setters, on the other hand, allow checks for correctness of the assigned value or notify listeners when the change takes place.

The other reason to prefer methods over fields can be found in the JVM specification. You’re permitted to move a method from a class to one of its superclasses and still maintain binary compatibility. So, a method initially introduced as Dimension javax.swing.JComponent.getPreferredSize(Dimension d) can be deleted in a new version and moved to Dimension java.awt.Component.getPreferredSize(Dimension d), as the JComponent is a subclass of Component. A change like this really happened in JDK 1.2, and this could be done only because the field was encapsulated by a method. An operation like this isn’t allowed for fields. Once a field is defined in a class, it has to stay there forever to maintain binary compatibility, which is another reason to keep fields private.

IT IS NOT SLOWER!

Once I heard a .NET architect confess his need to sacrifice encapsulation for performance. He said that .NET has a data structure with a few fields that were accessed so often that wrapping them with getters and setters caused a significant performance impact on the speed of the application. Well, maybe .NET needs to do that. Bear in mind that Java doesn’t need such tricks at all.

In the autumn of 1998, I visited a Sun Microsystems conference in Berlin, where JavaSoft’s boss said something like, “Yes, interpreted code can be faster than code that is compiled.” At that time I wasn’t working for Sun yet, and I also knew how terribly slow Java was. So I laughed, along with the majority of the audience. However, since then I’ve spent time thinking about those words. In the meantime, the JVM saw significant improvements when Sun released its HotSpot virtual machine. HotSpot first interprets bytecode provided by class files and then monitors the application to find critical pieces of its code and its setup. After some time, it compiles the parts of the application that are executed most frequently. But unlike with static compilation, which precedes linkage, the HotSpot compiler already knows the result of a linkage because the classes are already loaded. So, the HotSpot compiler can optimize the compiled code for the current setup and not for the general scenario as a regular compiler has to do.

As a result, the HotSpot compiler can, for example, inline virtual methods. Anyone who has tried to simulate virtual methods in C knows that this requires at least one access to memory and one indirect jump. The HotSpot compiler can do much better. As it knows the linkage information, it can find out that some virtual method isn’t overridden at all, or that, for example, two other classes override it twice. Then it can replace the regular call directly with the method’s body. Alternatively, if there is more than one, but less than many, it can replace the memory indirection with a simple switch. This is a lot faster than the plain virtual methods table that static compilation or a programmer trying to simulate this in C has to use.

Though it’s true that interpreted code is unlikely to be faster than code that is compiled, if we correct the statement I had heard in Berlin a bit, we can justifiably say, “Dynamically compiled code can be faster than code that is statically compiled,” which was likely the point of the talk to begin with. We can, with the help of the HotSpot compiler, see that this statement has turned out to be true.

Remember that the HotSpot virtual machine is capable of inlining method calls, including virtual method calls if the method is being called too often. As a result, frequently used getters and setters have no performance impact, as HotSpot eliminates them. Thus, there is no need to sacrifice API encapsulation for performance in Java.

Never expose fields in an API, except for public static final primitive or string constants, enum values, or immutable object references. Instead, always use methods to access your fields. If you follow this advice, the next version of the API can alter method content, provide validations and verifications, overload methods to do other wild things, move methods to superclasses, and change the synchronization model.

A Factory Is Better Than a Constructor

You facilitate an API’s future evolution when you expose a factory method rather than a constructor. Once a constructor is available in an API, it guarantees not only that an instance assignable to a given class will be created, but also that the instance will be of the exact class, because no subclasses are allowed. In addition, a new instance will be created every time.

If a factory method is provided instead, you have more flexibility. A factory method is usually a static method that takes the same arguments as the constructor and returns an instance of the same class in which the constructor is defined. The first advantage of factory methods is that you don’t need to return the exact class. You can instead return a subclass, which lets you use polymorphism and possibly clean up the code. Second, you can cache instances. Although in the case of a constructor a new instance is created every time, a factory method can cache previously instantiated objects and reuse them to save memory. Third, you can synchronize better when invoking a factory method, unlike with a plain constructor where this is more limited. That’s because a factory method can be synchronized as a whole, including potential code before the object creation, the code that creates the instances, as well as the rest of the code that runs after the object is created. This isn’t possible in a constructor at all.

We recently found yet another benefit of factory methods as we were rewriting the NetBeans APIs to use generics: factory methods let you “think up” the parameterized type for the returned object, and constructors don’t. For example, we’ve had the following class in the NetBeans APIs for ages:

public final class Template extends Object {
   private final Class type;

   public Template(Class type) { this.type = type; }
   public Class getType() { return type; }


   public Template() { this(Object.class); }
}

When we migrated the NetBeans sources to JDK 1.5, it was natural to parameterize the Template class with a type parameter identifying the internal class object. This worked fine . . .

public final class Template<T> extends Object {
   private final Class<T> type;

   public Template(Class<T> type) { this.type = type; }
   public Class<T> getType() { return type; }

   // now what!?
   public Template() { this(Object.class); }
}

. . . up until the last constructor. It’s supposed to create an instance of Template<Object>, but this cannot be expressed in Java 1.5. The language isn’t flexible enough to express this kind of situation. This might be seen as our design mistake. However, when we initially designed this class, we had more pressing things to solve than verifying that it would easily fit into the Java 1.5 generic type system that had not yet been designed. Later, it was too late. The only chance we had was to deprecate the constructor, use an unchecked cast, and tell people to use the other one, with a class parameter. On the other hand, if we had followed the advice of this book—using a factory method instead of exposing the constructor—our situation would have been much better. There would be a create() factory method, and it would be much easier to generify such a method, as the following code is syntactically correct:

public final class Template<T> extends Object {
   private final Class<T> type;

   public Template(Class<T> type) { this.type = type; }
   public Class<T> getType() { return type; }

   @Deprecated
   @SuppressWarnings("unchecked")
   public Template() { this((Class<T>)Object.class); }

   public static Template<Object> create() {
      return new Template<Object>(Object.class);
  }
}

This example demonstrates that there is much more flexibility when typing methods. That’s because methods—including factory methods—aren’t constrained by the type of the enclosing class, while constructors are. This is another reason to prefer factory methods over constructors.

Make Everything Final

Often, people don’t design with subclassing in mind, but allow it regardless. Unintended and dangerous consequences follow from this, as the number of ways in which the API can be used increases significantly. Take a simple method in a subclassable class such as this:

public class Hello {
   public void hello() { System.out.println ("Hello"); }
}

This method can cause problems, because external code can call it:

public static void sayHello() {
   Hello hello = new Hello();
   hello.hello();
}

Meanwhile, you can also override it to do something else:

private static class MyHello extends Hello {
   @Override
   public void hello() { System.out.println ("Hi"); }
}

It can also be overridden with a call to super:

private static class SuperHello extends Hello {
   @Override
   public void hello() {
     super.hello();
     System.out.println("Hello once again");
   }
}

More than likely, a far wider range of choices is presented here than was originally intended. However, the solution is simple: simply make the Hello class final!

When writing an API without wanting to let users subclass its classes, it’s better to disallow this behavior. If you don’t explicitly disallow it, you can be sure that some of your API users will subclass your API. As the Hello example shows, you’ll then need to support at least three different ways of using the methods that your classes expose. For the sake of future evolution, it’s better to disallow subclassing. In relation to this, also see Chapter 8.

Again, the simplest solution is to make your class final. Other approaches include non-public constructors, which you should use anyway (as explained in the section “A Factory Is Better Than a Constructor”), or making all—or at least most—of your methods final or private.

Of course, this only works for classes. If you decide to use interfaces, you cannot forbid foreign implementations on the level of the virtual machine. You can only ask people in the Javadoc not to do it. The Javadoc isn’t a bad channel for communication, but if subclassing isn’t prohibited people will always ignore the Javadoc advice and implement the interface any-way. Consequently, unless there are performance implications, it’s preferable to prevent the subclassability on the virtual machine level by using final classes.

Do Not Put Setters Where They Do Not Belong

One lesson we learned the hard way after years of NetBeans API development is, “Don’t put setter methods in the true API.” By “true API,” we mean the interfaces that must be implemented to provide something. If setter methods are needed at all—and usually they aren’t—they belong only in the convenience base classes.

Let’s look at javax.swing.Action as an example that violates this rule. The setEnabled(boolean) method doesn’t belong in the Action interface. Probably it shouldn’t exist at all, but if so, it should be a protected method in AbstractAction, which is a support class to simplify implementation of the Action interface. The boolean isEnabled() method is the API! It’s the method that everyone should call. But who is supposed to call setEnabled(boolean)? At most only the creator of the action; this is an implementation detail for everyone else. If your action happens to have a global enablement state that a listener normally manipulates by turning it on or off programmatically, setEnabled(boolean) is a handy protected final method to have in a base class such as AbstractAction. However, Action.setEnabled is wrong for the following reasons:

  • It implies that unknown external clients should call this method. They certainly should not.
  • It makes no sense for context-sensitive actions, though the Action API generally is lacking in this area. We tried hard to find some reasonable way to use Swing’s actions in this area and we can certainly report a success. However, it’s clear that the original Action class design was done by people who didn’t have to think about modular design at all.
  • It makes no sense to have a setEnabled method for an action that is always enabled. Those actions just want to return true from isEnabled regardless of anything that happens around them.
  • It makes no sense to have an action whose enablement is computed in isEnabled on demand. If you want to compute the state of your actions just when you are asked to do it, then a setter isn’t the right method to use. If you need to implement a “pull” strategy, then you want to provide the result, but not pretend you know anything about the global state of the action. Instead, you want the system to query you for the right state again when the value of isEnabled is needed.

This advice isn’t meant to discredit every setter in an API. Sometimes they’re useful. Sometimes, as in the case of Spring’s dependency injections, they’re essential. However, often they’re redundant. Similar examples of too many setters can be found in other places and they’re usually the result of a desire to make the API rich with functionality, which is in fact counterproductive. Maintenance becomes increasingly cumbersome. In addition, as shown in the Action example, the semantics of such an API are hard to grasp. The advice here is simple: beware of unneeded setters in an API.

Allow Access Only from Friend Code

When trying to prevent exposing too much in an API, another useful technique is to give access to certain functionality only to “friend” code. Functionality that falls within this category could include the ability to instantiate a class or to call a certain method.

By default, Java restricts the friends of a class to those classes that are in the same package. If you want to share functionality just among classes in the same package, use the package private modifier in the definition of a constructor, field, or method. These will then remain accessible only to their “friends.”

NO MORE “DO NOT CALL THIS METHOD!” METHODS

You’re likely to have come across APIs with methods marked in the Javadoc with sentences like, “Do not call me, I am part of the implementation” or “Only for internal use.” I’ve seen such methods so many times that I consider this one of the worst API design antipatterns. Not only do these methods make an API less professional, they distract the reader of the API with unnecessary implementation details that are easy to avoid.

Methods of this kind usually imply that parts of the implementation residing in separate packages need privileged—that is, friend—access to functionality provided by the API package. This is exactly the case for which the friend accessor pattern was invented.

Some claim that the friend accessor pattern is too complicated and that they don’t want complicated code in their API. Nothing is further from the truth! It’s true that the friend accessor pattern in Java is slightly complex to code. However, that remains just an implementation detail, invisible to the external API user. It achieves the cluelessness goal, giving users of your API fewer methods to worry about and a greater opportunity of being cluelessly successful in using your API.

Therefore, please don’t include “I am an implementation detail” methods in your APIs!

It might sometimes be useful to extend a set of friends to a wider range of classes. For example, you might want to define two packages, one for the pure API and the other for the implementation. If you take this approach, the following trick might be useful. Imagine there is an Item class, as follows:

public final class Item {
   private int value;
   private ChangeListener listener;

   static {
       Accessor.setDefault(new AccessorImpl());
   }

   /** Only friends can create instances. */
   Item() {
   }

   /** Anyone can change value of the item.
    */
   public void setValue(int newValue) {
       value = newValue;
       ChangeListener l = listener;
       if (l != null) {
           l.stateChanged(new ChangeEvent(this));
       }
   }

   /** Anyone can get the value of the item.
    */
   public int getValue() {
       return value;
   }

   /** Only friends can listen to changes.
    */
   void addChangeListener(ChangeListener l) {
       assert listener == null;
       listener = l;

   }
}

This class is part of the API, but cannot be instantiated or listened to outside the friend classes that are in the API package and other packages. In this scenario, you can define an Accessor in the non-API package:

public abstract class Accessor {
   private static volatile Accessor DEFAULT;
   public static Accessor getDefault() {
       Accessor a = DEFAULT;
       if (a != null) {
           return a;
       }

       try {
           Class.forName(
               Item.class.getName(), true, Item.class.getClassLoader()
           );
       } catch (Exception ex) {
           ex.printStackTrace();
       }

       return DEFAULT;
   }

   public static void setDefault(Accessor accessor) {
       if (DEFAULT != null) {
           throw new IllegalStateException();
       }
       DEFAULT = accessor;
   }

   public Accessor() {
   }

   protected abstract Item newItem();
   protected abstract void addChangeListener(Item item, ChangeListener l);
}

This Accessor has abstract methods to access all the “friend” functionality of the Item class, with a static field to get the accessor’s instance. The main trick is to implement the Accessor with a nonpublic class in the API package:

final class AccessorImpl extends Accessor {
   protected Item newItem() {
       return  new Item();
   }

   protected void addChangeListener(Item item, ChangeListener l) {
       item.addChangeListener(l);
   }
}

You register the Accessor as the default instance the first time someone touches api.Item. Do this by adding a static initializer to the Item class:

static {
   Accessor.setDefault(new AccessorImpl());
}

Now the friend code can use the accessor to invoke the hidden functionality from the impl package:

Item item = Accessor.getDefault().newItem();
assertNotNull("Some item is really created", item);

Accessor.getDefault().addChangeListener(item, this);

SECURE CODE WITH RUNTIME CONTAINERS

A runtime container, such as the one in the NetBeans Platform, can secure your code even further. You can forbid access to the impl package on the class-loading level, by specifying OpenIDE-Modules-Public-Packages: api.**. The Accessor methods need not be protected, as only the module that defines the impl.Accessor can access that class. Access from other modules would then be disallowed on the class-loading level.

Future versions of Java might provide an additional access modifier to allow mutual access among multiple packages compiled together. However, why wait? The new access modifier will likely require changes in the virtual machine, which will likely prevent the code using it from running on older versions of Java, including Java 6. The accessor pattern replaces a special language construct with an intrinsic arrangement of classes, which makes it work on any version of the JVM while achieving almost the same result. I explicitly say “almost,” as one thing cannot be directly mapped to this pattern: the new access modifier allows code in a friend package to extend classes in the API package, which others might not even see. However, this kind of limitation is easy to eliminate: instead of using subclasses, use delegation, as suggested in the section “Delegation and Composition” in Chapter 10.

From time to time I refer to this kind of API design pattern as a teleinterface, as illustrated in Figure 5-1. Similar to a spaceship in any good sci-fi book, traveling in hyperspace from one open gateway to another, it is invisible and unobservable when outside regular space. The teleinterface pattern allows two distinct parties to communicate so that no one else can observe the interaction, but it’s known to exist.

Fig1.jpg

Figure 5-1. Teleinterface

The friend accessor is an example of such an API design pattern. Two sides need to com- municate with each other, while no external piece of code is able to monitor the interaction. It’s carried out in “hyperspace.” This is a powerful design metapattern, and this book will introduce several kinds of teleinterfaces.

Give the Creator of an Object More Rights

Not every user of an object should have access to all its functionality. “Some code is more equal than other code.” This is an old object-oriented truth embedded in the roots of many object-oriented languages. As a result, C++, Java, and similar languages contain access modifiers that define who should access what. Java offers public, protected, package private, and private, which respectively grant access to everyone, to subclasses and code in the same package, only to code in the same package, and only to code in the same class. This is a fine-grained set of controls. Some developers, such as those who come from the Smalltalk world where everything is public, might even complain that this is too complicated and unnecessary. On the other hand, even this set of access modifiers might be seen as limiting. For example, the section “Allow Access Only from Friend Code” shows that there’s often a need to give more access rights to “friend packages.” This section talks about yet another common case in which one piece of code needs more privileges than another piece of code.

No one with a basic knowledge of Unix—or any other nonpersonal computer system targeted for use by multiple users—should be surprised that certain operations can be performed only by a restricted set of individuals known as system administrators. It’s fine to query a database or to publish your own web pages under the $HOME/public_html folder. Typically, everyone is allowed to do that. However, the right to configure the caching policy of your Apache web server, to modify the database, or (imagine how daring!) to touch and change its schema, is usually given only to a selected subset of people. Before performing these kinds of operations, those individuals need to prove to the system that they have the right to perform the operation in question—that is, that they have the appropriate identity. Everyone can try to lay claim to having the appropriate identity. However, without being party to a secret, the false claim is unlikely to succeed. Typical authentication is based on secret name-and-password pairs with roles assigned to each name.

The world of object-oriented systems is similar. Objects travel between various parts of a system, and to ensure certain invariants and the basic consistency of their internal states, it isn’t wise to let every piece of code in the system modify and configure each object. In fact, as mentioned before, access modifiers are your best friend. If you don’t want something to be visible to external code, don’t make that functionality public or protected. However, this turns your code into the “administrator,” with everyone else being regular users. This might work in in-house systems. However, when designing APIs this isn’t enough, because you don’t want your API to be an “administrator.” Instead, you want your API to be a library that serves administrators as well as regular users. Let’s look at ways you can achieve this.

First of all, let’s see what doesn’t work. A common attempt at separating the rights of a creator from the API is to create an API interface or class together with a support class that extends it. The support class then provides additional controls, specifically for the creator. All the other methods in the API are then supposed to use the base interface class, while  the implementors use the support class to easily implement the contract and also to access additional methods that aren’t available for nonprivileged users. This was probably the idea behind javax.swing.text.Document and its support pair class javax.swing.text.AbstractDocument. It more or less worked, because subclassing AbstractDocument is simpler than trying to implement all its methods in the plain Document interface. Also, AbstractDocument contains methods accessible only to implementors, such as protected writeLock and writeUnlock. This looks reasonable, but it restricts access only to subclasses. So, it’s common to find code like the following, which exposes the additional “creator only” methods to all the code in the same package, which is often needed:

public class MyDocument extends AbstractDocument {
   public MyDocument() {super(new StringContent());}

   final void writeLockAccess() {
       writeLock();
   }
}

It isn’t nice to force API users of AbstractDocument to duplicate methods such as this one. A better-designed API might eliminate this verbosity. However, as this is just a stylistic issue, it isn’t the main problem.

The biggest problem with AbstractDocument is that it contains a handful of other useful methods that are of interest to “nonprivileged” API users. These include the publicly accessible readLock, readUnlock (why use render and create an unnecessary Runnable anyway?), getListeners (why not find out whether I am the first listener, and if not rearrange all listeners so that I am?), or methods such as replace that were probably added too late to fit into the original Document interface. In short, there are several interesting methods that the clients calling into the javax.swing.text API might want to call. What are such users to do? They take an instance of a document and cast it to AbstractDocument and call those methods. Where is the security? If it was as easy for a nonadministrator to log in to a server as it is to call a “privileged” method on AbstractDocument, every server on the Internet would already have been hacked. That’s why this pattern of an API interface with a support class doesn’t solve the “privileged” access problem.

NETBEANS FILESYSTEM API

You might well ask how I can claim to know so much about Swing’s text document API problems. In fact, to be honest, I don’t! I know a lot about the NetBeans FileSystem API, where I made a similar mistake. I then searched the JDK sources for similar patterns, and AbstractDocument was a nice and handy anti-example.

The NetBeans FileSystem API introduces the concept of a virtual filesystem. You can build such a filesystem around a plain java.io.File. Alternatively, it can wrap access to HTTP or FTP servers, or it can represent completely virtual files that reside only in memory. In each case, the base API interface is org.openide.filesystems.FileSystem, which is intended for regular API users. However, because implementing a filesystem isn’t easy, we also have a support class that helps implementors, called org.openide.filesystems.AbstractFileSystem. This, by chance, used to have several interesting methods not available in the base FileSystem interface. Moreover, there was a public LocalFileSystem derived from it with additional useful public methods.

Users of that API were supposed to work with plain FileSystem, but one day I found to my horror that they weren’t doing that! Because the NetBeans IDE works almost solely with plain java.io.File, it was quite safe to cast to LocalFileSystem, after which some fancy methods could be called on it. These methods were intentionally not available to the public. They were intended to be accessible only to “privileged” users. At the time, I shortsightedly believed that no one else would call them. However, API users are quite inventive, especially when they stick to the cluelessness principle while doing whatever it takes to get their job done quickly.

We needed to prevent people from calling these methods. The first step we took was a little bit of bytecode patching. Our bytecode patching converted these methods to protected while retaining backward binary compatibility. Later, we stopped using LocalFileSystem for the representation of local files. We replaced it with a different implementation not derived from AbstractFileSystem, which wasn’t part of any API. Users had no need to cast to it, as it didn’t offer anything worth calling.

A common way of customizing objects in Java is to turn them into JavaBeans and then to use setters and getters. However, this provides no security and no “privileged” role for an object’s creator. However, with a surprisingly simple addition, you can turn this setter-based pattern into a nicely working solution supporting the “privileged” mode. All you need to do is add the method public void setReadOnly() and then modify all setters to work only before the method is called. Then throw an exception; for example, PropertyVetoException or IllegalStateException. You can find an example of this pattern in java.security. PermissionCollection, which allows the collection’s creator to add new permissions to it and then make it immutable by calling setReadOnly. Every other piece of code that gets hold of such objects later can only read the state of the collection, without the right to modify it. In effect, it is executed in nonprivileged mode. This approach works fine and the only complaint you can have about it is that it isn’t elegant. Of course, as discussed in the section “Beauty, Truth, and Elegance” in Chapter 1, software design isn’t about beauty. Nevertheless, this solution does have some practical drawbacks. First, it mixes the API for two groups of people. Both privileged as well as regular users need to read the API of the same class, which might be distracting. Second, it introduces a state-based API. The same sequence of method calls might work or might throw an exception, depending on whether the object is still in privileged mode or has already been switched to regular mode. This makes the object’s semantics more complicated than necessary. Last but not least, the switch to regular mode can be done just once and definitively. Once an object is switched, there is no privileged code anymore. This might be an unwanted restriction in certain situations. Due to all this, let’s continue our investigation of the other available solutions.

There is a member of every class in Java, as well as in other object-oriented languages, that can be called just once by the object’s creator. It’s the constructor. This fact gives us the basis for another straightforward separation for the privileged mode: everything the privileged code can control needs to be passed in when an object is created. Of course, as suggested in the section “A Factory Is Better Than a Constructor,” it’s better to have a factory method than a constructor, although the principle remains the same. In contrast to the previous “setter-based” solution, this solves the semantic problems, as it isn’t stateful. Everything the privileged code can do is injected into the object at the time of its creation. Also, it partially solves the problem of mixed APIs. When users of an API have a reference to an instance of an object, they usually don’t read documentation for its constructor, and it isn’t offered to them by code completion in modern development environments. Also, factories and constructors are well-understood concepts. This solution is natural because it doesn’t invent anything new. In short, this is probably the most appropriate solution.

However, is this solution ready for evolution? What if someone needs to add new methods to the privileged code? In such cases, you need to add a new constructor or factory method that accepts the additional arguments. This is backward binary compatible, but each such addition increases the size of the factory class. But that isn’t the problem. The more complicated issue is that potentially the number of arguments in such methods can be significant. Long lists of arguments, especially of the same type, aren’t pleasant to fill in. It’s easy to make a typo and misplace an argument. This is usually not an issue in the beginning. However, as the API evolves it can get quite complicated. It’s desirable to have a solution ready for such situations, such as mixing the factory style with the setter-based approach. Simply write the following:

public static Executor create(Configuration config) {
   return new Fair(config);
}

public static final class Configuration {
   boolean fair;
   int maxWaiters = -1;

   public void setFair(boolean fair) {
       this.fair = fair;
   }
   public void setMaxWaiters(int max) {
       this.maxWaiters = max;
   }
}

With this approach you can stop adding new factory methods, and if evolution requires it, add new setters to the Configuration class instead. Adding such methods is safe, as the class is final and the only use for it is as an argument to the create factory method. Its usefulness is restricted outside this scenario, as it intentionally has no public getters. That’s why adding new methods to it can only affect the internals of the factory method, which are under the API writer’s control. You can therefore extend the internals to handle the additional variations in the input configuration.

Moreover, introducing the Configuration class solves the last outstanding issue—that is, the ability of the privileged code to perform its privileged operation after the nonprivileged code has already accessed the object. This was impossible with the setReadOnly pattern, as well as with the plain factory method. Now, the privileged code can continue to keep a reference to the Configuration instance and call its methods to modify parameters of Executor instances already given to nonprivileged users. Such users’ code has no chance to get a reference to the Configuration instance; casting doesn’t help because these are two different objects. The Configuration acts like a secret token, which cannot be obtained through an API. So, this solution is completely safe and secure.

MUTEX AND PRIVILEGED ACCESS

We used to face similar issues with the NetBeans APIs. We had our own homemade read/write lock called the Mutex, whose methods took Runnable and could execute them in read or write mode. This worked fine. However, we found that in some situations the high number of newly created runnables impacted performance. We considered adding the following typical methods:

lock.enterReadAccess();
 try {
  // do the operation
 } finally {
   lock.exitReadAccess();
}

However, we were afraid that this could allow malicious code to forget to release acquired locks, thus breaking the consistency of the whole subsystem coordinated with such a lock. But we were sure that our own code would contain no such bugs, so we decided to use the privileged creator pattern.

We kept the original signatures of Mutex that only worked with Runnable, but added a Mutex. Privileged static inner class that had the enter and exit methods and supported the more efficient “enter/try/perform/finally/exit” lock access. We added a new constructor to Mutex to take the Mutex.Privileged instance:

private static final Mutex.Privileged PRIVILEGED = new Mutex.Privileged();
public static final Mutex MUTEX = new Mutex(PRIVILEGED);

Now, any creator of a public Mutex can create a back door giving its code a faster—but more error prone—access to synchronization, while ensuring that nonprivileged code isn’t able to maliciously spoil the consistency of locking.

Although Java doesn’t have a special access modifier for the creator of objects, it’s possible to create an API that ensures such a contract. There are many solutions, ranging from the simplest and ugliest to the most complex and elegant. The most promising option, in my opinion, is to have two classes: one giving privileged access to the creator and the other providing a public API for common use.

Do Not Expose Deep Hierarchies

Some people argue that object-oriented languages are good because they support code reuse. It’s true that certain code patterns are better expressed in object-oriented programming languages than in plain old C. However, that doesn’t improve the code’s “sharability,” because the main problem faced when sharing code is communication. There has to be someone offering something to share and someone else absorbing and reusing it. This isn’t going to happen simply by using object-oriented languages. Instead, you need to think about reuse and prepare for it, while bearing in mind that it will never automatically happen simply as a     by-product. My advice here is to be careful, and on top of everything else, to be aware of deep hierarchies.

Classic object-oriented programming languages are said to be inspired by nature and its evolution. Everyone knows the example defining a Mammal class with basic methods describing the behavior of every mammal, and various subclasses such as Dog or Cat overriding the methods to do what cats and dogs do differently. This example not only demonstrates what object-oriented languages do best, but it also justifies their whole purpose. By showing how easy it is to describe nature, the object-oriented concept is also elevated to be the right pro- gramming technique to describe the real world. Just like physics is better for reasoning about the real world than plain geometry, object-oriented languages are supposed to be more suitable than traditional, procedural ones for coding and capturing real-world concepts.

After maintaining a framework in Java for more than ten years, I don’t buy this. It’s fine that something (usually a class) reacts to messages (in Java method calls) and that there might be someone who can intercept that message and change its meaning by overriding some of the methods and reacting differently to them. However, honestly, this is nothing other than an intricate switch statement. A method call isn’t a direct invocation of some code but rather a switch that chooses from multiple implementations. The switch is based on the list of sub-classes and can potentially grow as various developers create more and more subclasses. But it’s still a switch. Of course, explaining object-oriented programming as an enhanced switch is much less fancy than presenting it as a technology that helps us model the real world. That’s why computer courses are likely to stick with the Mammal example. However, I’ll use the switch explanation to reveal an important hidden catch: when writing an API, the size and behavior of the switch is unknown to the writer of the code. This can be seen as an advantage, increasing the number of ways that a piece of code can be reused. However, a few releases later, if you don’t know the actual participants in the switch, you can have a nightmare on your hands. Simply allowing an unknown party to participate in your code “switch” can be too open to be maintainable.

So, simply exposing a deep hierarchy of classes is unlikely to improve an API’s usability. When writing an API, the term “subclass” shouldn’t be used for someone who can “participate in a switch,” but for someone who behaves exactly like me, plus adds some additional behavior. True, many would agree that a Human is a subclass (a specialization) of a Mammal. But how much can you use this fact in an API design? Do you really want to claim that a JButton is a specialization of AbstractButton and that it’s a specialization of JComponent, which is a specialization of a Container, which is a specialization of a Component? Of course not! The subclassing here is simply an implementation detail. That is, it’s a switch statement enabling you to code things in a simple way. However, it has nothing to do with the notion of an API. If it did, it would be normal to use a JButton as a Container. In other words, you could add additional components to it. Even though it might be possible to code in this way, this isn’t at all the intended API usage of JButton.

Here we can identify a common API flaw in object-oriented languages. If your implementation can benefit from subclassing another class, don’t expect that everyone else, and eventually the whole API and its clients, can benefit from exposing that class. If the subclass is there only to implement the switch statement, this is unlikely to happen. The advice here is: instead of big, rooted families, try to define the real interface of the application and let users implement it. Always remember that if something is a subclass or a subinterface, it can inherently be used in place of the original superclass or interface.

Be aware of situations similar to Frame indirectly extending Component. Given all the API rules, this relationship should mean that you can use a Frame in all places where Component is used, even though this isn’t true at all. The fact that the frame inherits from the component is just an implementation detail, just a switch statement to reuse some of the component code. However, it has nothing to do with the actual API. This type of object-oriented reuse, which would better be called misuse, is common, especially in cases of deep class or interface hierarchies. That’s why if the inheritance hierarchy is deeper than two, it’s useful to stop and ask yourself, “Am I doing this for the API or just for the code reuse?” If the answer is the latter, rewrite the API to be stricter or to be explicitly ready for subclassing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.0.53