Bessere Schnittstellen

Letztens hatte ich in einer Klasse eine Methode überschrieben, um zu verhindern, das ein bestimmter Wert unterschritten wird.

public class FontPreviewPanel extends JPanel {
 ...
  @Override
  public void setSize(Dimension d)
  {
    if (d.width <= 0)
      d.width = 1;
    if (d.height <= 0)
      d.height = 1;
    super.setSize(d);
  }
}

Die Maßnahme zeigte auch seine gewollte Wirkung. Doch dann fiel mir noch ein, dass die Super-Klassen ein weiteres setSize besitzen:

public void setSize(int width, int height);

Muss ich mir da jetzt Sorgen machen?

Was ist, wenn jemand die zweite Methode aufruft? Also die mit zwei int-Parametern? Wird dann meine Ein-Parameter-Variante indirekt aufgerufen? Oder ist es vielleicht umgekehrt? Oder wird meine überhauptnicht aufgerufen?

Ich könnte in den fremden Sourcecode schauen und mich daran orientieren, ob und welche ich letztendlich überschreiben muss. Aber ist das auch in der nächsten Version garantiert gleich? Eher nicht.

Ich habe dann die zweite ebenfalls überschrieben. 🙁

Es dürfte klar sein, das eine Variante zu viel da ist. Laut Java API Dokumentation sind beider Methoden seit JDK 1.1 dabei. Man kann also nicht mal sagen, das eine zuerst „unglücklich“ gewählt wurde, und später eine bessere dazu kam. Das würde man ja noch verstehen können. Aber so?

Bevorzugen Sie Strukturen als Parameter-Typ

Die int-Parameter-Variante macht keinen Sinn. Wenn ich Schnittstellen definiere, dann nur so, das ich nur eine Variante habe. Und diese versuche ich so weit wie möglich zu spezialisieren.

Die Dimension-Parameter-Methode ist viel einfacher zu verstehen. Sie sorgt auch für eine bessere Kompatibilität. Was ist, wenn ich für setSize einen dritten Parameter benötige?

// Version 1.0:
void setSize(int w, int h);

// Version 2.0:
void setSize(int w, int h, int z);

Dann wird die Schnittstelle inkompatibel! Also muss man beide Versionen bereitstellen. Und das wird die API aufblähen.

Besser ist die Variante mit dem Dimension-Parameter:

// Version 1.0:
class Dimension {
   public Dimension(int w, int h);
}
void setSize(Dimension d);
// Version 2.0, Version 1.0 entfällt:
class Dimension {
   public Dimension(int w, int h);
   public Dimension(int w, int h, int z);
}
// Schnittstelle bleibt gleich:
void setSize(Dimension d);

Hier bleibt die Schnittstelle gleich. Auch für den Aufrufer ändert sich nichts, obwohl setSize in der nächsten Version mehr kann:

setSize( new Dimension(1,2) ); // egal ob Version 1 oder 2.

Außer man will den zusätzlichen Parameter nutzen:

setSize( new Dimension(1,2,3) ); // Version 2.

Versuchen Sie immer Objekte/Strukturen als Parameter-Typ zu benutzen. Sollte es eine Erweiterung geben, müssen Sie nur den Parameter-Typ und nicht die Schnittstelle ändern.

Übrigens, die Windows API gibt es seit über 30 Jahren. Und sie basiert genau auf diesem Konzept, ohne das sie inkompatibel wird.