Blogs:JesseGlick:OfClassesAndInterfaces

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(New page: == Jesse's thoughts about Classes and Interfaces == Someone asked: <blockquote> ''I guess that, roughly, a client API should use abstract classes while a support/prov...)
Current revision (06:07, 15 July 2008) (edit) (undo)
 
Line 13: Line 13:
<source lang="java">
<source lang="java">
-
package javax.whatnot;
+
package javax.whatnot;
-
public final class Stuff {
+
public final class Stuff {
-
private StuffProvider impl;
+
private StuffProvider impl;
-
private Stuff(StuffProvider impl) {...}
+
private Stuff(StuffProvider impl) {...}
-
public static Stuff forPlace(Place place) {
+
public static Stuff forPlace(Place place) {
-
return new Stuff(findImplForPlace(place));
+
return new Stuff(findImplForPlace(place));
-
}
+
}
-
public String name() {
+
public String name() {
-
return impl.name();
+
return impl.name();
-
}
+
}
-
}
+
}
-
package javax.whatnot.spi;
+
package javax.whatnot.spi;
-
public interface StuffProvider {
+
public interface StuffProvider {
-
String name();
+
String name();
-
}
+
}
</source>
</source>
Line 33: Line 33:
<source lang="java">
<source lang="java">
-
package javax.whatnot;
+
package javax.whatnot;
-
public final class Stuff {
+
public final class Stuff {
-
private StuffProvider impl;
+
private StuffProvider impl;
-
private Stuff(StuffProvider impl) {...}
+
private Stuff(StuffProvider impl) {...}
-
public static Stuff forPlace(Place place) {
+
public static Stuff forPlace(Place place) {
-
return new Stuff(findImplForPlace(place));
+
return new Stuff(findImplForPlace(place));
-
}
+
}
-
public String name() {
+
public String name() {
-
return impl.name();
+
return impl.name();
-
}
+
}
-
public long distance() {
+
public long distance() {
-
return impl instanceof PossiblyDistantStuffProvider ?
+
return impl instanceof PossiblyDistantStuffProvider ?
-
((PossiblyDistantStuffProvider) impl).distance() :
+
((PossiblyDistantStuffProvider) impl).distance() :
-
/* fallback value */ 0L;
+
/* fallback value */ 0L;
-
}
+
}
-
}
+
}
-
package javax.whatnot.spi;
+
package javax.whatnot.spi;
-
public interface StuffProvider {
+
public interface StuffProvider {
-
String name();
+
String name();
-
}
+
}
-
public interface PossiblyDistantStuffProvider extends StuffProvider {
+
public interface PossiblyDistantStuffProvider extends StuffProvider {
-
String name();
+
String name();
-
long distance();
+
long distance();
-
}
+
}
</source>
</source>
Line 62: Line 62:
<source lang="java">
<source lang="java">
-
package javax.whatnot;
+
package javax.whatnot;
-
public abstract class Stuff {
+
public abstract class Stuff {
-
public Stuff forPlace(Place place) {...}
+
public Stuff forPlace(Place place) {...}
-
protected Stuff() {}
+
protected Stuff() {}
-
public abstract String name();
+
public abstract String name();
-
public long distance() {/*fallback*/ return 0L;}
+
public long distance() {/*fallback*/ return 0L;}
-
}
+
}
</source>
</source>
Line 76: Line 76:
<source lang="java">
<source lang="java">
-
package api;
+
package api;
-
public Icon getIcon() {
+
public Icon getIcon() {
-
return implV1 != null ? implV1.getImageIcon() : implV2.getIcon();
+
return implV1 != null ? implV1.getImageIcon() : implV2.getIcon();
-
}
+
}
-
public ImageIcon getImageIcon() {
+
public ImageIcon getImageIcon() {
-
return implV1 != null ? implV1.getImageIcon() :
+
return implV1 != null ? implV1.getImageIcon() :
-
new ImageIcon(icon2Image(implV2.getIcon()));
+
new ImageIcon(icon2Image(implV2.getIcon()));
-
}
+
}
</source>
</source>
Line 89: Line 89:
<source lang="java">
<source lang="java">
-
public interface TotallyDifferentStuffProvider {
+
public interface TotallyDifferentStuffProvider {
-
long xCoord();
+
long xCoord();
-
long yCoord();
+
long yCoord();
-
}
+
}
</source>
</source>
Line 98: Line 98:
<source lang="java">
<source lang="java">
-
name() -> googleMapsWebServices.locate(impl.xCoord(), impl.yCoord()).
+
name() -> googleMapsWebServices.locate(impl.xCoord(), impl.yCoord()).
-
nameOfMunicipality()
+
nameOfMunicipality()
-
distance() -> sqrt(impl.xCoord()**2 + impl.yCoord()**2)
+
distance() -> sqrt(impl.xCoord()**2 + impl.yCoord()**2)
</source>
</source>

Current revision

Jesse's thoughts about Classes and Interfaces

Someone asked:

I guess that, roughly, a client API should use abstract classes while a support/provider API should use interfaces?

to which I responded:

Generally the client API should use final classes (or static methods where appropriate) and conceal all references to the SPI. For example, say you started with

package javax.whatnot;
public final class Stuff {
  private StuffProvider impl;
  private Stuff(StuffProvider impl) {...}
  public static Stuff forPlace(Place place) {
    return new Stuff(findImplForPlace(place));
  }
  public String name() {
    return impl.name();
  }
}
package javax.whatnot.spi;
public interface StuffProvider {
  String name();
}

and later changed this to:

package javax.whatnot;
public final class Stuff {
  private StuffProvider impl;
  private Stuff(StuffProvider impl) {...}
  public static Stuff forPlace(Place place) {
    return new Stuff(findImplForPlace(place));
  }
  public String name() {
    return impl.name();
  }
  public long distance() {
    return impl instanceof PossiblyDistantStuffProvider ?
      ((PossiblyDistantStuffProvider) impl).distance() :
      /* fallback value */ 0L;
  }
}
package javax.whatnot.spi;
public interface StuffProvider {
  String name();
}
public interface PossiblyDistantStuffProvider extends StuffProvider {
  String name();
  long distance();
}

Compared to

package javax.whatnot;
public abstract class Stuff {
  public Stuff forPlace(Place place) {...}
  protected Stuff() {}
  public abstract String name();
  public long distance() {/*fallback*/ return 0L;}
}

the API/SPI split gives you a lot more options for how to deal with compatibility. You can have different versions of the SPI that have quite different signatures. A single abstract class is hard to make work in such situations - you can add one method with a simple fallback easily enough, but then you can get into trouble if you want to do anything more complicated, like change the return type of a method.

We ran into this very problem long ago when we realized an abstract class in our API was mistakenly using ImageIcon instead of Icon for the return type of a method - oops! It was very hard to fix. (You can have the default impl of both versions of the method delegate to the other one, but then you need to use reflection to make sure at least one method was actually overloaded, else you get a stack overflow...) With an API/SPI split, it would have been trivial, and more comprehensible for SPI users too:

package api;
public Icon getIcon() {
  return implV1 != null ? implV1.getImageIcon() : implV2.getIcon();
}
public ImageIcon getImageIcon() {
  return implV1 != null ? implV1.getImageIcon() :
    new ImageIcon(icon2Image(implV2.getIcon()));
}

In the previous example, consider wanting to add to the SPI

public interface TotallyDifferentStuffProvider {
  long xCoord();
  long yCoord();
}

where in the API

name() -> googleMapsWebServices.locate(impl.xCoord(), impl.yCoord()).
           nameOfMunicipality()
distance() -> sqrt(impl.xCoord()**2 + impl.yCoord()**2)

Personally I don't see any compelling reason to use abstract classes in a public API. There might be weird cases (related to security?) where you can take advantage of the protected access mode.

--JesseGlick 09:33, 15 June 2008 (UTC)

Personal tools
buy