SOLID: Interface Segregation Principle

In diesem Blogpost geht es um das vierte der fünf SOLID-Prinzipien, das Interface Segregation Principle. Oder zu deutsch etwa das "Schnittstellen-Auftrennungs-Prinzip".

Das Prinzip - exemplarisch

Clients should not be forced to depend on methods that they do not use.

Ein Autohändler möchte sich eine Software-Lösung zur Verwaltung des Kataloges, also welche Autos verkauft werden, entwickeln lassen. Der Autohändler bietet eine breite Bandbreite von Autos an. Basierend darauf wird folgendes Interface entwickelt, das für alle Automodelle passen soll:
(Hinweis: Dies ist ein C#-Style Interface mit Properties, mit Java hätte man stattdessen je einen Getter/Setter für jedes Property)

Die Properties FuelTankSize und FuelType werden benötigt, um anzugeben, wie gross der Tank eines Benzin- oder Dieselautos ist, und was für einen Treibstoff man tanken muss. AccumulatorCapacity und PlugType sind wichtig für elektrische Autos: Wie gross die Akkukapazität ist und mit was für einem Stecker es aufgeladen wird.

Jedes Fahrzeug, dass der Autohändler verkauft muss also dieses Interface implementieren. Damit könnte also eine Klasse für einen Benziner etwa so aussehen:

public class GasolineCar : ICar {
   public decimal Price { get; set; }

   public double MaxSpeed { get; set; }

   public CarDimensions Size { get; set; }

   public double Weight { get; set; }

   public double FuelTankSize { get; set; }

   public FuelType FuelType {
      get { return FuelType.Gasoline; }
   }

   public double AccumulatorCapacity { get; set; }

   public PlugType PlugType {
      get { return PlugType.None; }
   }
}

Und für Elektro-Autos:

public class ElectricCar : ICar {
   public decimal Price { get; set; }

   public double MaxSpeed { get; set; }

   public CarDimensions Size { get; set; }

   public double Weight { get; set; }

   public double FuelTankSize { get; set; }

   public FuelType FuelType {
      get { return FuelType.None; }
   }

   public double AccumulatorCapacity { get; set; }

   public PlugType PlugType {
      get { return PlugType.SAE_J1772; }
   }
}

Bei dieser Implementation kann man nun aber ein paar Probleme feststellen:

  • Jede Art von Auto muss das Property AccumulatorCapacity, also die Kapzität der Batterie implementieren. Zwar haben sowohl Benziner wie auch elektrische Autos eine Batterie, aber sie hat bei den beiden Typen eine andere Bedeutung. Beim elektrischen Auto könnte man zum Beispiel von der Akkukapazität herleiten, wie gross die Reichweite des Autos ist, bei einem Benziner jedoch nicht.
  • Jede Art Auto muss die readonly-Properties FuelType und PlugType implementieren. Jedoch haben Benziner keinen Stecker um die Batterie zu laden, und elektrische Autos haben keinen Treibstoff. Somit muss deswegen ein Enum-Literal zur Verfügung gestellt werden, welches ausdrückt, dass diese Art Auto gar keinen entsprechenden Typ hat.
  • Wenn nun ein neues elektrisches Auto instanziert wird, kann man von diesem Auto die Grösse des Treibstofftankes abfragen und/oder setzen, obwohl es gar keinen solchen Tank hat.

All diese Probleme können vermieden werden, indem das Interface Segregation Principle befolgt wird: Statt einem grossen Interface werden mehrere kleine Interfaces deklariert, so dass jede Klasse exakt bestimmen kann, welche Interfaces für sie relevant sind. Für dieses Beispiel könnte das im Klassendiagramm etwa so aussehen: