My Wiki!

Book: practical API design

Do not expose more than you want

Use methods instead of instances

  • getters & setters

Factory is better than Constructors

Problem:

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); }
}

Solution:

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);
}
}

Use final everywhere

Prohibit sub-classing with final key word if it's not desired.

Allow Access Only from Friend Code

Friend accessor example:

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() {CHAPTER 5DO NOT EXPOSE MORE THAN YOU WANT
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);

Give the creator of an object more control

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 refer- ence 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 refer- ence 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.

Code against interface not implementation

Interfaces for immutable

Adding methods to interface requires that all client implementations of that interface to be changed. The interface must be subclassed in order to provide additional method:

public interface InstanceProvider {
  public Class<?> instanceClass() throws Exception;
  public Object instanceCreate() throws Exception;
}
 
public interface BetterInstanceProvider extends InstanceProvider {
  public boolean isInstanceOf(Class<?> c);
}

Now all the clients around our code, as well as in all modules written by third parties, are supposed to check if the provider they have reference to is “better” by doing instanceof, and if so, cast and invoke its isInstanceOf method.

This creates new problems. First of all, all client code using just InstanceProvider will be rewritten. This is not possible, as the code is spread all over the world and nobody has complete control over it, so it’s still likely clients will be using the old approach and thus performance won’t improve. Also, the client code is getting more and more complicated. Every client has to do the instanceof check and handle both alternatives. If it’s true, it needs to use the new method; if it fails, it needs to revert back to the old behavior of myClass. isAssignableFrom(provider.instanceClass()). Also, spreading such “if” statements throughout the entire code base isn’t nice at all.

if (instance instanceof BetterInstanceProvider) {
BetterInstanceProvider bip = (BetterInstanceProvider)instance;
return bip.isInstanceOf(String.class);
} else {
return String.class.isAssignableFrom(instance.instanceClass());
}

That’s why I prefer Java interfaces to clearly specify an interface that isn’t about to change.

A Method Addition Lover’s Heaven

While Java interfaces are completely immutable with respect to method addition, there’s the opposite extreme. In this scenario, it’s not only possible to add methods, but it’s also possible to make them completely binary compatible. In Java this can be expressed in the form of the final class.

Therefore, when you need to add a new method to a class or interface, select the final class. With the final class, you won’t encounter the problem of complicated client code that was described in the interfaces section. Say the InstanceProvider is a final class:

import java.util.concurrent.Callable;
public final class InstanceProvider {
private final Callable<Object> instance;
public InstanceProvider(Callable<Object> instance) {
this.instance = instance;
}
public Class<?> instanceClass() throws Exception {
return instance.call().getClass();
}
public Object instanceCreate() throws Exception {
return instance.call();
}
}

Then, it’s easy and safe to add new methods to it and provide a default implementation, so that the class becomes the following:

import
import
import
import
java.util.Arrays;
java.util.HashSet;
java.util.Set;
java.util.concurrent.Callable;
public final class InstanceProvider {
private final Callable<Object> instance;
private final Set<String> types;
public InstanceProvider(Callable<Object> instance) {
this.instance = instance;
this.types = null;
}
/** Specifies not only a factory for creating objects, but
* also additional information about them.
* @param instance the factory to create the object
* @param type the class that the create object will be instance of
* @since 2.0
*/
public InstanceProvider(Callable<Object> instance, String... types) {
this.instance = instance;
this.types = new HashSet<String>();
this.types.addAll(Arrays.asList(types));
}
public Class<?> instanceClass() throws Exception {
return instance.call().getClass();
}
public Object instanceCreate() throws Exception {
return instance.call();
}
/** Allows to find out if the InstanceProvider creates object of given
* type. This check can be done without loading the actual object or
* its implementation class into memory.
*
* @param c class to test
* @return if the instances produced by this provider is instance of c
* @since 2.0
*/
public boolean isInstanceOf(Class<?> c) throws Exception {
if (types != null) {
return types.contains(c.getName());
} else {
// fallback
return c.isAssignableFrom(instanceClass());
}
}
}

The difference with the interfaces example is that the clients of the API in version 2.0 don’t need to care about whether the actual instance handles their call to isInstanceOf in a better way or if it’s a fallback to the old implementation.

Are Abstract Classes Useful?

Up until this point, it has been argued that you should use Java interfaces when you want to define an immutable contract and that you should use Java final classes when you want to have the ability to add methods. So it's reasonable to ask, “Is there any reason to use abstract classes?”.

The short answer is “No”. But abstract class allows defining static methos, which is not possible with interface.

Getting Ready for Growing Parameters

For example, say you have a method that was called by the framework to compute the content of a UI list. Later you realize that you not only need that content, but you also need some additional text to describe or classify the computed data. If that happens, you can add a new method that calls the previous one, as follows:

public abstract class Compute {
/**
* @return list of strings to work with
* @since 1.0 */
public abstract List<String> getData();
/
** Computes the strings to work with together with their
* associated descriptions. Shall be overriden in subclasses.
* By default delegates to {@link #getData}
* and uses the provided strings as both, the string
* and its description.
*
* @return name to description pairs to work with
* @since 2.0 */
public Map<String,String> getDataAndDescription() {
LinkedHashMap<String,String> ret =
new LinkedHashMap<String, String>();
for (String s : getData()) {
ret.put(s, s);
}
return ret;
}
}

This is possible, but only if the method is inside a class. On top of that, giving clients a class to implement isn’t as clean a solution as giving them an interface. Also, adding methods into a class that is often subclassed is slightly dangerous. That is why it’s preferred to use a Java interface for the Compute class. However, then you can give up on the need to add new meth- ods. Although it’s possible to create an extended interface, a much better situation, with respect to evolution, can be created by means of the response/reply pattern:

public interface Compute {
public void computeData(Request request, Response response);
public final class Request {
// only getters public, rest hidden only for friend code
Request() {
}
}
public final class Response {
// only setters public, rest available only for friend code
private final Map<String,String> result;
/** Allow access only to friend code */
Response(Map<String,String> result) {
this.result = result;
}
public void add(String s) {
result.put(s, s);
}
public void addAll(List<String> all) {
for (String s : all) {
add(s);
}
}
/** @since 2.0 */
public void add(String s, String description) {
result.put(s, description);
}
}
}

In this setup, you can easily achieve any extension to the method parameters by adding new getters into the Compute.Request class, which is immutable and final, and therefore its methods can only be called. That means that you can add more methods there without any risk.

interfaces vs. Classes

In summary, always code against interfaces and not implementations. Remember, this doesn’t mean Java interfaces, but abstract definitions. When coding in Java, use Java interfaces to specify immutable types and use final classes as the type where methods can be safely added. When designing more intricate structures, think about extensibility and choose the appropri- ate style, just like in the request and response example. Sometimes it’s acceptable to use abstract classes. However, remember that if the goal is 100 percent binary compatibility, never add methods to classes or interfaces that someone else can implement.

Javaworld - Java design articles

Sources

Design techniques articles

object initialization

Two important design guidelines this article attempts to promote are the “canonical object design” and “omni-valid state principle.” The canonical object design An object should have state, represented by instance variables that are private. Invoking an object's instance methods should be the only way code defined in other classes can affect the object's state.

The omni-valid state principle Objects should have a valid state, and experience only valid state transitions, from the beginning of their lifetimes to the end.

  • With respect to object initialization, this article suggests a number of handy guidelines:
  • The no-way-to-create-an-object-with-an-invalid-state guideline – Design an object's initializers and constructors such that the object cannot possibly be created in an invalid state.
  • The not-natural rule of thumb – If an instance variable doesn't have a natural default value, force client programmers to pass an initial value (or data from which an initial value can be calculated) to every constructor in the class.
  • The exceptional rule concerning bad constructor input – Check for invalid data passed to constructors. On detecting invalid data passed to a constructor, throw an exception.
  • The valid guideline about invalid objects – Sometimes it will make sense to design a class whose instances can, at times, be in an invalid, unusable state. For such classes, throw an exception when a method can't perform its normal duties because the object's state was invalid when the method was invoked.
  • The hold-your-horses-during-initialization guideline – Constructors and initializers should be about initialization, the whole initialization, and nothing but initialization.

One last guideline pertains to the nature of guidelines themselves. The guidelines proposed in this column are not proposed as laws you should blindly follow at all times but as rules of thumb you'll probably want to follow much of the time. They are intended to help you acquire a mindset conducive to good design. Thus, the final guideline is:

  • The guideline guideline – All guidelines proposed by this column should be disregarded some of the time – including this one.

Designing fields & methods

This article covered some very fundamental territory, which can be summarized as three guidelines: The especially valuable guideline for fields

  • Don't assign a special meaning to special values of a field in an effort to represent multiple attributes with the single field. Instead, use a separate field to represent each separate attribute of a class or object.

The constant corollary

  • Prefer constants (static final fields) over hard-coded literals (1.5, “Hi there!”), especially if you need to use the same constant value in multiple places.
  • The minimize-coupling mantra
  • Always strive to minimize the coupling of methods: Take as input only data needed by the method; express as output only data produced by the method.

Maximizing method cohesion & avoiding method explosion

The gist of this article can be summarized in these guidelines:

  • The maximize-cohesion mantra Always strive to maximize the cohesion of methods: Focus each method on one conceptual task, and give it a name that clearly indicates the nature of that task.
  • The watch-what's-going-down guideline Avoid passing data used for control (for deciding how to perform the method's job) down into methods.
  • The method-explosion-aversion principle Balance maximizing method cohesion (which can increase the number of methods in a class) with keeping the number of methods in a class to a manageable level.
  • The golden rule When designing and coding, do unto other programmers (who will be maintaining your code) as you would have them do unto you (if you were to maintain their code).

Objects finalization and clean up

The most important point to take away from this article is that if a Java object needs to take some action at the end of its life, no automatic way exists in Java that will guarantee that action is taken in a timely manner. You can't rely on finalizers to take the action, at least not in a timely way. You will need to provide a method that performs the action and encourage client programmers to invoke the method when the object is no longer needed.

This article contained several guidelines that pertain to finalizers:

  • Don't design your Java programs such that correctness depends on “timely” finalization
  • Don't assume that a finalizer will be run by any particular thread
  • Don't assume that finalizers will be run in any particular order
  • Avoid designs that require finalizers to resurrect objects; if you must use resurrection, prefer cloning over straight resurrection

Remember that exceptions thrown by finalizers are ignored If your program includes objects with finalizers that absolutely must be run before the program exits, invoke runFinalizersOnExit(true) in class Runtime or System Unless you are writing the finalizer for class Object, always invoke super.finalize() at the end of your finalizers

Designing with exceptions

The most important point to take away from this article is that exceptions are there for abnormal conditions and shouldn't be used to report conditions that can be reasonably expected as part of the everyday functioning of a method. Although the use of exceptions can help make your code easier to read by separating the “normal” code from the error handling code, their inappropriate use can make your code harder to read.

Here is a collection of the exception guidelines put forth by this article:

  • If your method encounters an abnormal condition that it can't handle, it should throw an exception. Avoid using exceptions to indicate conditions that can reasonably be expected as part of the normal functioning of the method.
  • If your method discovers that the client has breached its contractual obligations (for example, by passing in bad input data), throw an unchecked exception.
  • If your method is unable to fulfill its contract, throw either a checked or unchecked exception.
  • If you are throwing an exception for an abnormal condition that you feel client programmers should consciously decide how to handle, throw a checked exception.
  • Define or choose an already existing exception class for each kind of abnormal condition that may cause your method to throw an exception.

Design for thread safety

The most important point to take away from this article is that when programming in Java, you should at least think about thread safety every time you design a class.

Here's a collection of the exception guidelines put forth by this article:

  • Given that thread safety can have a performance cost, don't make every class thread-safe – only those classes that will actually be used concurrently by multiple threads
  • Don't avoid making classes thread-safe that need to be thread-safe out of fear of a performance impact
  • When making an object thread-safe via Approach 1, synchronize only the critical sections of the class
  • Use an immutable object especially if the object is small or represents a fundamental data type
  • If you can't change a non-thread-safe class, use a wrapper object
  • If you are creating a library of classes that will be used in both thread-safe and non-thread-safe requirements, consider making wrappers an option

The event generator idiom

http://www.artima.com/designtechniques/eventgenP.html

Implementation guidelines

With the guidelines I list in this section, I am trying to define a default way to implement this idiom. I say default because, unless you have a specific reason to take a different implementation approach, you should automatically use the approach recommended in these guidelines. My theory is that if you adhere closely to the default implementation approach, it will be easier for your fellow programmers to recognize the idiom in your work. More importantly, I feel that such idiom recognition will make it easier for your fellow programmers to understand, use, and change your code.

On the other hand, you should feel free to depart from the default approach to implementing the idiom when you feel it makes sense. In fact, I myself describe two potential “variants” to the default approach in the next section.

Now, on to the guidelines:

  • Create just one event object for each “firing” and pass it to all listeners.
  • Make the event object immutable, so there is no possibility that a listener will change the event object as it propagates.
  • Use a single thread to notify all listeners. In other words, the fire method should go through the list of listeners and invoke the appropriate handler method upon one listener after the other.
  • Take a snapshot (clone the list) of registered listeners at the beginning of the firing process, then send the event to each listener registered at the time the snapshot was taken.
  • Keep event handlers (listener methods) short. These methods should execute quickly because (in the default approach) listeners are notified one at a time. In other words, listeners must wait until all listeners before them in the queue have been notified, so it is good citizenship for listeners to be quick about handling events. If an event handler method really needs to do considerable work as a result of an event notification, consider designing the handler such that it fires off or notifies another thread that does the actual time-consuming work.
  • Don't write listeners such that they depend on an order of notification.
  • If a listener is not interested in all events that compose a particular event category, consider subclassing an adapter class and just overriding the methods of interest.

Composition versus inheritance

Comparing composition and inheritance

So how exactly do composition and inheritance compare? Here are several points of comparison:

  • It is easier to change the interface of a back-end class (composition) than a superclass (inheritance). As the previous example illustrated, a change to the interface of a back-end class necessitates a change to the front-end class implementation, but not necessarily the front-end interface. Code that depends only on the front-end interface still works, so long as the front-end interface remains the same. By contrast, a change to a superclass's interface can not only ripple down the inheritance hierarchy to subclasses, but can also ripple out to code that uses just the subclass's interface.
  • It is easier to change the interface of a front-end class (composition) than a subclass (inheritance). Just as superclasses can be fragile, subclasses can be rigid. You can't just change a subclass's interface without making sure the subclass's new interface is compatible with that of its supertypes. For example, you can't add to a subclass a method with the same signature but a different return type as a method inherited from a superclass. Composition, on the other hand, allows you to change the interface of a front-end class without affecting back-end classes.
  • Composition allows you to delay the creation of back-end objects until (and unless) they are needed, as well as changing the back-end objects dynamically throughout the lifetime of the front-end object. With inheritance, you get the image of the superclass in your subclass object image as soon as the subclass is created, and it remains part of the subclass object throughout the lifetime of the subclass.
  • It is easier to add new subclasses (inheritance) than it is to add new front-end classes (composition), because inheritance comes with polymorphism. If you have a bit of code that relies only on a superclass interface, that code can work with a new subclass without change. This is not true of composition, unless you use composition with interfaces. Used together, composition and interfaces make a very powerful design tool. I'll talk about this approach in next month's Design Techniques article.
  • The explicit method-invocation forwarding (or delegation) approach of composition will often have a performance cost as compared to inheritance's single invocation of an inherited superclass method implementation. I say “often” here because the performance really depends on many factors, including how the JVM optimizes the program as it executes it.
  • With both composition and inheritance, changing the implementation (not the interface) of any class is easy. The ripple effect of implementation changes remain inside the same class.

Choosing between composition and inheritance

So how do all these comparisons between composition and inheritance help you in your designs? Here are a few guidelines that reflect how I tend to select between composition and inheritance.

  • Make sure inheritance models the is-a relationship My main guiding philosophy is that inheritance should be used only when a subclass is-a superclass. In the example above, an Apple likely is-a Fruit, so I would be inclined to use inheritance.
  • An important question to ask yourself when you think you have an is-a relationship is whether that is-a relationship will be constant throughout the lifetime of the application and, with luck, the lifecycle of the code. For example, you might think that an Employee is-a Person, when really Employee represents a role that a Person plays part of the time. What if the person becomes unemployed? What if the person is both an Employee and a Supervisor? Such impermanent is-a relationships should usually be modelled with composition.
  • Don't use inheritance just to get code reuse If all you really want is to reuse code and there is no is-a relationship in sight, use composition.
  • Don't use inheritance just to get at polymorphism If all you really want is polymorphism, but there is no natural is-a relationship, use composition with interfaces. I'll be talking about this subject next month.

Designing with interface

Interface guidelines Where, then, do interfaces fit into this picture? As I mention above, one major benefit of the Java interface is that they give composition a shot at polymorphism. When you use composition with interfaces, it becomes as easy to add a new front-end class (composition) as it is to add a new subclass (inheritance). But what does this tell us? Should you always use interfaces every time you use composition? Well, no. Should you avoid using interfaces in conjunction with single inheritance of class extension? Certainly not.

As I mentioned at the beginning of this article, it took me a long time to get the point of interfaces. The epiphany finally came when I recognized that separation interface and implementation is one of the primary ideas behind Java in general. The Java virtual machine (JVM), for example, is an abstract computer that defines the way your program “interfaces” with the underlying real computer. A JVM that runs on Windows is one implementation of that abstract computer. A JVM that runs on the Macintosh is another. A JVM that runs on your wristwatch is yet another.

Likewise, the Java APIs are designed not to give you access to specific capabilities of particular computers and operating systems, but define abstract interfaces through which your programs talks to the underlying concrete computer and operating system, whatever it is. Swing, for example, provides an interface through which your Java program can create graphical user interfaces on whatever platform happens to be underneath. You can even use Swing to create user-interfaces on your wristwatch, so long as someone has done the work to implement Swing on your wristwatch.

Separation of interface and implementation is central to Java's spirit, and the Java interface construct enables you to achieve this separation in your designs. Two major activities of any software system design are identifying parts (the subsystems within a program or system of programs) and specifying the interfaces between the parts. In designing a Java-based system, you should use Java interfaces to represent abstract interfaces – the ways in which the parts will interact with each other.

So this is how I ended up thinking about Java's interfaces: as the preferred means of communicating with the parts of your program that represent abstractions that may have several implementations. For example, two parts of a program I describe in my September Design Techniques installment, “The Event Generator Idiom”, were TelephoneListener and Telephone. In this design, I decided that the “telephone listener” represented an abstraction that could have multiple implementations, but that Telephone did not. Thus, I made Telephone a class that didn't implement any interfaces, and defined TelephoneListener as an interface. Telephone, an event source, passed events to (communicated with) listeners through the TelephoneListener interface.

I see interfaces as a fundamental tool for achieving flexibility in the design of Java-based systems. Any class can provide an implementation of an interface. As long as you don't change the interface itself, you can make all kind of changes to the implementing classes, or plug in new classes, without impacting code that depends only on the interface. Thus, if you have a subsystem that represents an abstraction that may have multiple implementations, whether the subsystem is a single object, a group of objects, an entire Java applet or application, you should define Java interfaces through which the rest of the world communicates with that subsystem. When you use interfaces in this way, you decouple the parts of your system from each other and generate code that is more flexible: more easily changed, extended, and customized.

Class vs object


Navigation