The Capability Pattern: Future-Proof Your APIs
SwingWorker.
It contains a class called TaskStatus, which abstracts the
task status data from the task-performing object itself. It is a simple
interface with setters that allow a background thread to inform
another object (presumably a UI) about the progress of a task.
In light of what we just discussed, TaskStatus really
ought to be a final class. So let's rewrite it a little, to
look like this. We will use a mirror-class for the SPI.
public final class TaskStatus {
private final StatusImpl impl;
TaskStatus (StatusImpl impl) {
this.impl = impl;
}
public void setTitle (String title) {
impl.setTitle (title);
}
public void setProgress (String msg, long progress, long min, long max) {
//We could do argument sanity checks here and make life
//simpler for anyone implementing StatusImpl
impl.setProgress (msg, progress, min, max);
}
public void setProgress (String msg) {
//...you get the idea
//...
}
public abstract class StatusImpl {
public abstract void setTitle (String title);
public abstract void setProgress (String msg, long progress, long min, long max);
public abstract void setProgress (String msg); //indeterminate mode
public abstract void done();
public abstract void failed (Exception e);
}
So we have an API that handles basic status display. But people are going to invent new aspects to status display. We can't save the world and solve everybody's task-status problems before they even think of them - and we shouldn't try. We don't want to set things up so that it's up to us to implement everything the world will ever want. Luckily, it doesn't have to be that way.
Since we've designed our API so that it can be compatibly added to, we let the rest of the world come up with things they need for displaying task status, and the ones that a lot of people need can be added to our API in the future. The capability pattern lets us do that. We add two methods to our API and SPI classes:
public abstract class StatusImpl {
//...
public <T> T getCapability (Class<T> type);
}
public final class TaskStatus {
//...
public <T> T getCapability (Class<T> type) {
return impl.getCapability (type);
}
}
Let's put that to practical use. Someone might
want to display how much time remains
before the task is done. Our API doesn't handle that.
Through the capability pattern, we can add that. We
(or anyone implementing StatusImpl) can
create the following interface:
public interface StatusTime {
public void setTimeRemaining (long milliseconds);
}
A task that wants to provide this information to the UI, if the
UI supports it, simply does this:
public T runInBackground (TaskStatus status) {
StatusTime time = status.getCapability (StatusTime.class);
for (...) {
//do some slow work...
if (time != null) {
long remaining = //estimate the time remaining
time.setTimeRemaining (remaining);
}
}
}
Even better, our Task API is, right now, not tied
specifically to Swing or AWT - it could be used for anything
that needs to follow the pattern of computing something on a
background thread and then doing work on another one. Why not
keep it un-tied to UI toolkits? All we have to do is make the
code that actually handles the threading pluggable (I'll talk
about how you do this simply using the Java classpath for dependency
injection in my next blog). Then the result could be used with
SWT or Thinlet as well, or even in a server-side application.
Instead of a SwingWorker, we have an
AnythingWorker!
- 6207 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)









