Delegationsmuster in Kotlin

1. Übersicht

Es gibt viele Anwendungsfälle, in denen die Delegierung der Vererbung vorgezogen wird. Kotlin hat dafür eine großartige Unterstützung auf Sprachebene.

In diesem Tutorial werden wir über Kotlins native Unterstützung für das Delegierungsmuster sprechen und es in Aktion sehen.

2. Implementierung

Nehmen wir zunächst an, wir haben ein Codebeispiel mit der folgenden Struktur in einer Bibliothek eines Drittanbieters:

interface Producer { fun produce(): String } class ProducerImpl : Producer { override fun produce() = "ProducerImpl" }

Als nächstes dekorieren wir die vorhandene Implementierung mit dem Schlüsselwort "by" und fügen die zusätzliche erforderliche Verarbeitung hinzu:

class EnhancedProducer(private val delegate: Producer) : Producer by delegate { override fun produce() = "${delegate.produce()} and EnhancedProducer" }

In diesem Beispiel haben wir angegeben, dass die EnhancedProducer- Klasse ein Delegatenobjekt vom Typ Producer kapselt . Außerdem können Funktionen aus der Producer- Implementierung verwendet werden.

Lassen Sie uns abschließend überprüfen, ob es wie erwartet funktioniert:

val producer = EnhancedProducer(ProducerImpl()) assertThat(producer.produce()).isEqualTo("ProducerImpl and EnhancedProducer")

3. Anwendungsfälle

Schauen wir uns nun zwei gängige Anwendungsfälle für das Delegierungsmuster an.

Erstens können wir das Delegierungsmuster verwenden , um mehrere Schnittstellen unter Verwendung vorhandener Implementierungen zu implementieren :

class CompositeService : UserService by UserServiceImpl(), MessageService by MessageServiceImpl()

Zweitens können wir die Delegierung verwenden , um eine vorhandene Implementierung zu verbessern .

Letzteres haben wir im vorherigen Abschnitt getan. Ein realistischeres Beispiel wie das folgende ist jedoch besonders nützlich, wenn wir eine vorhandene Implementierung nicht ändern können - beispielsweise Bibliothekscode von Drittanbietern:

class SynchronizedProducer(private val delegate: Producer) : Producer by delegate { private val lock = ReentrantLock() override fun produce(): String { lock.withLock { return delegate.produce() } } }

4. Delegation ist keine Vererbung

Jetzt müssen wir uns immer daran erinnern, dass der Delegierte nichts über den Dekorateur weiß. Daher sollten wir nicht den GoF-Vorlagenmethoden- ähnlichen Ansatz mit ihnen ausprobieren .

Betrachten wir ein Beispiel:

interface Service { val seed: Int fun serve(action: (Int) -> Unit) } class ServiceImpl : Service { override val seed = 1 override fun serve(action: (Int) -> Unit) { action(seed) } } class ServiceDecorator : Service by ServiceImpl() { override val seed = 2 }

Hier verwendet der Delegat ( ServiceImpl ) eine Eigenschaft, die in der allgemeinen Schnittstelle definiert ist, und wir überschreiben sie im Dekorator ( ServiceDecorator ). Dies hat jedoch keine Auswirkungen auf die Verarbeitung des Delegaten:

val service = ServiceDecorator() service.serve { assertThat(it).isEqualTo(1) }

Schließlich ist zu beachten, dass wir in Kotlin nicht nur an Schnittstellen delegieren können, sondern auch Eigenschaften trennen können .

5. Schlussfolgerung

In diesem Tutorial haben wir über die Kotlin-Schnittstellendelegierung gesprochen - wann sie verwendet werden sollte, wie sie konfiguriert wird und welche Einschränkungen sie aufweist.

Wie üblich ist der vollständige Quellcode für diesen Artikel auf GitHub verfügbar.