Standardimplementierung in Protokollen umsetzen

Protokolle sind eine großartige Technik wenn es darum geht, Eigenschaften und Funktionen zu definieren, ohne eine Implementierung dafür anbieten zu müssen. Daraufhin kann man Typen ein oder mehrere Protokolle zuweisen, zu denen sie konform sein müssen, und genau die kümmern sich dann um eine passende (und meist individuelle) Implementierung.

So weit, so gut. Doch es gibt Szenarien, in denen die fehlende Implementierung innerhalb eines Protokolls ein störender Faktor sein kann der dazu führt, dass verschiedene Typen zu einem bestimmten Protokoll konform sind und für die im Protokoll definierten Eigenschaften und Funktionen eine identische Implementierung anbieten. In der Regel merkt man in diesem Szenario als Entwickler recht schnell, dass es eigentlich sehr praktisch wäre, würde das Protokoll bereits über eine Standard-Implementierung verfügen, die automatisch alle Typen erhalten, die konform zum jeweiligen Protokoll sind. Falls diese Standard-Implementierung nicht passt ließe die sich dann ja noch immer überschreiben und passend ersetzen, ohne dass das Mehraufwand bedeuten würde; die Implementierung muss ja so oder so durchgeführt werden.

Genau dieses beschriebene Szenario – eine Standard-Implementierung für die Eigenschaften und Funktionen eines Protokolls anzubieten – ist mit Swift möglich. Das Zauberwort hierbei lautet Extensions.

Erweiterung von Protokollen

Betrachten wir zum besseren Verständnis direkt ein konkretes Beispiel. Das folgende Listing zeigt ein Protokoll namens Vehicle, das insgesamt drei Eigenschaften definiert: Eine veränderbare Property currentSpeed, die die aktuelle Geschwindigkeit eines Fahrzeugs darstellen soll, eine Read-Only-Property maximumSpeed, die die Höchstgeschwindigkeit abbildet, und eine Funktion namens maximumSpeedDescription(), die die Höchstgeschwindigkeit in formatierter Form auf der Konsole ausgibt.

protocol Vehicle {
    var currentSpeed: UInt { get set }
    var maximumSpeed: UInt { get }
    func maximumSpeedDescription() -> String
}

In diesem Beispiel gehen wir davon aus, dass generell die Höchstgeschwindigkeit für alle Fahrzeuge, die mithilfe des Vehicle-Protokolls abgebildet werden, 200 km/h beträgt. Des Weiteren soll die mittels maximumSpeedDescription() zurückgegebene Beschreibung standardmäßig schlicht „Höchstgeschwindigkeit: <i> km/h“ lauten, wobei <i> für den Wert der maximumSpeed-Property steht.

Anstatt also diese beiden Eigenschaften in jedem Typ immer wieder identisch und auf exakt dieselbe Art und Weise zu implementieren, nutzt man stattdessen eine Extension, die man auf das Vehicle-Protokoll anwendet und darin die Standard-Implementierung für die gewünschten Elemente unterbringt. Das folgende Listing zeigt, wie sich das auf Basis des genannten Beispiels umsetzen lässt.

extension Vehicle {
    var maximumSpeed: UInt {
        return 200
    }
    func maximumSpeedDescription() -> String {
        return "Höchstgeschwindigkeit: \(maximumSpeed) km/h"
    }
}

Erstellt man nun einen Typ, der konform zum Vehicle-Protokoll ist, muss dieser lediglich noch die currentSpeed-Property auf eine eigene Art und Weise implementieren. Bietet man keine angepasste Implementierung für maximumSpeed und maximumSpeedDescription() an, wird bei entsprechendem Zugriff darauf auf die Logik der Protokoll-Extension zurückgegriffen. Das verdeutlicht das folgende Listing, in dem eine Structure namens Car erstellt wird, die konform zum Vehicle-Protokoll ist. Trotz fehlender Implementierung für maximumSpeed und maximumSpeedDescription() stehen diese beiden Eigenschaften zur Verfügung und liefern ein passendes Ergebnis.

struct Car: Vehicle {
    var currentSpeed: UInt = 0
}
let myCar = Car()
print("\(myCar.maximumSpeedDescription())")
// Höchstgeschwindigkeit: 200 km/h

Wie zuvor erwähnt ändert die Protokoll-Extension aber nichts daran, dass man die darin implementieren Eigenschaften und Funktionen auch innerhalb der protokollkonformen Typen noch einmal selbst auf eine alternative Art und Weise implementieren kann, wie das folgende Listing demonstriert. Darin bietet Car eine eigene Implementierung für die Property maximumSpeed an, die sich auf die Ausgabe der Funktion maximumSpeedDescription() auswirkt.

struct Car: Vehicle {
    var currentSpeed: UInt = 0
    var maximumSpeed: UInt {
        return 160
    }
}
let myCar = Car()
print("\(myCar.maximumSpeedDescription())")
// Höchstgeschwindigkeit: 160 km/h

Fazit

Die Möglichkeit, eine Standardimplementierung für die Eigenschaften und Funktionen eines Protokolls anzubieten, gehört zu den größten Vorteilen und Stärken von Swift. Zwar ist eine solche nicht für jedes Protokoll sinnvoll, gerade aber in Projekten, in denen man in protokollkonformen Typen immer und immer wieder die gleiche Implementierung für bestimmte Eigenschaften und Funktionen durchführt, kann es womöglich sinnvoll sein, eine passende Standardimplementierung im Protokoll selbst anzubieten. Falls diese nicht passt, kann die schließlich auch immer noch innerhalb eines entsprechenden Typs überschrieben werden.

Euer Thomas


Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert